summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java121
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java349
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl64
-rw-r--r--core/java/android/accounts/AccountAuthenticatorActivity.java2
-rw-r--r--core/java/android/accounts/AccountManager.java3
-rw-r--r--core/java/android/accounts/AccountManagerService.java12
-rw-r--r--core/java/android/accounts/ChooseAccountActivity.java8
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java17
-rw-r--r--core/java/android/animation/AnimatorInflater.java6
-rw-r--r--core/java/android/animation/FloatEvaluator.java8
-rw-r--r--core/java/android/animation/IntEvaluator.java8
-rw-r--r--core/java/android/animation/LayoutTransition.java326
-rw-r--r--core/java/android/animation/ObjectAnimator.java221
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java213
-rw-r--r--core/java/android/animation/TypeEvaluator.java4
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java8
-rw-r--r--core/java/android/app/ActionBar.java87
-rw-r--r--core/java/android/app/Activity.java10
-rw-r--r--core/java/android/app/ActivityGroup.java2
-rw-r--r--core/java/android/app/ActivityManager.java147
-rw-r--r--core/java/android/app/ActivityManagerNative.java127
-rw-r--r--core/java/android/app/ActivityThread.java102
-rw-r--r--core/java/android/app/ApplicationPackageManager.java59
-rw-r--r--core/java/android/app/ApplicationThreadNative.java6
-rw-r--r--core/java/android/app/FullBackupAgent.java76
-rw-r--r--core/java/android/app/IActivityManager.java19
-rw-r--r--core/java/android/app/IApplicationThread.java5
-rw-r--r--core/java/android/app/IBackupAgent.aidl20
-rw-r--r--core/java/android/app/IProcessObserver.aidl25
-rw-r--r--core/java/android/app/IThumbnailRetriever.aidl25
-rw-r--r--core/java/android/app/Instrumentation.java1
-rw-r--r--core/java/android/app/NotificationManager.java3
-rw-r--r--core/java/android/app/Service.java20
-rw-r--r--core/java/android/app/WallpaperManager.java8
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java14
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java3
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java39
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/app/backup/BackupAgent.java44
-rw-r--r--core/java/android/app/backup/FullBackup.java129
-rw-r--r--core/java/android/app/backup/FullBackupAgent.java225
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl46
-rw-r--r--core/java/android/app/backup/IFullBackupRestoreObserver.aidl69
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java2
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java9
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java44
-rw-r--r--core/java/android/content/ContentResolver.java5
-rw-r--r--core/java/android/content/ContentService.java5
-rw-r--r--core/java/android/content/Context.java5
-rw-r--r--core/java/android/content/IOnPrimaryClipChangedListener.aidl3
-rw-r--r--core/java/android/content/Intent.java32
-rw-r--r--core/java/android/content/IntentFilter.java9
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java13
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/PackageManager.java204
-rw-r--r--core/java/android/content/pm/PackageParser.java28
-rw-r--r--core/java/android/content/pm/ServiceInfo.java20
-rw-r--r--core/java/android/content/pm/UserInfo.aidl20
-rw-r--r--core/java/android/content/pm/UserInfo.java105
-rw-r--r--core/java/android/content/res/AssetManager.java3
-rw-r--r--core/java/android/content/res/Configuration.java53
-rw-r--r--core/java/android/content/res/StringBlock.java3
-rw-r--r--core/java/android/database/AbstractCursor.java3
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java3
-rw-r--r--core/java/android/database/DatabaseUtils.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java19
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java86
-rw-r--r--core/java/android/database/sqlite/SQLiteDirectCursorDriver.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java12
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java65
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java23
-rw-r--r--core/java/android/ddm/DdmHandleAppName.java3
-rw-r--r--core/java/android/ddm/DdmHandleExit.java3
-rw-r--r--core/java/android/ddm/DdmHandleHeap.java17
-rw-r--r--core/java/android/ddm/DdmHandleHello.java11
-rw-r--r--core/java/android/ddm/DdmHandleProfiling.java9
-rw-r--r--core/java/android/ddm/DdmHandleThread.java3
-rw-r--r--core/java/android/ddm/DdmRegister.java3
-rwxr-xr-xcore/java/android/gesture/Gesture.java2
-rw-r--r--core/java/android/hardware/Camera.java557
-rw-r--r--core/java/android/hardware/Sensor.java12
-rw-r--r--core/java/android/hardware/SensorEvent.java31
-rw-r--r--core/java/android/hardware/usb/UsbManager.java39
-rw-r--r--core/java/android/inputmethodservice/ExtractButton.java2
-rw-r--r--core/java/android/inputmethodservice/ExtractEditLayout.java183
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java33
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java57
-rw-r--r--core/java/android/net/ConnectivityManager.java135
-rw-r--r--core/java/android/net/IConnectivityManager.aidl15
-rw-r--r--core/java/android/net/INetworkPolicyListener.aidl24
-rw-r--r--core/java/android/net/INetworkPolicyManager.aidl (renamed from core/java/android/os/INetStatService.aidl)31
-rw-r--r--core/java/android/net/INetworkStatsService.aidl33
-rw-r--r--core/java/android/net/NetworkInfo.java22
-rw-r--r--core/java/android/net/NetworkPolicyManager.java93
-rw-r--r--core/java/android/net/NetworkState.aidl19
-rw-r--r--core/java/android/net/NetworkState.java68
-rw-r--r--core/java/android/net/NetworkStats.aidl19
-rw-r--r--core/java/android/net/NetworkStats.java273
-rw-r--r--core/java/android/net/NetworkStatsHistory.aidl19
-rw-r--r--core/java/android/net/NetworkStatsHistory.java340
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java54
-rw-r--r--core/java/android/net/SntpClient.java7
-rw-r--r--core/java/android/net/TrafficStats.java173
-rw-r--r--core/java/android/net/http/Headers.java5
-rw-r--r--core/java/android/net/http/HttpLog.java3
-rw-r--r--core/java/android/net/http/HttpResponseCache.java268
-rw-r--r--core/java/android/net/http/HttpsConnection.java6
-rw-r--r--core/java/android/nfc/ErrorCodes.java5
-rw-r--r--core/java/android/nfc/INdefPushCallback.aidl28
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl2
-rw-r--r--core/java/android/nfc/INfcTag.aidl5
-rw-r--r--core/java/android/nfc/NdefRecord.java112
-rw-r--r--core/java/android/nfc/NfcAdapter.java71
-rw-r--r--core/java/android/nfc/Tag.java46
-rw-r--r--core/java/android/nfc/tech/BasicTagTechnology.java5
-rw-r--r--core/java/android/nfc/tech/IsoDep.java10
-rw-r--r--core/java/android/nfc/tech/MifareUltralight.java14
-rw-r--r--core/java/android/nfc/tech/NfcF.java25
-rw-r--r--core/java/android/os/AsyncTask.java11
-rw-r--r--core/java/android/os/BatteryStats.java497
-rw-r--r--core/java/android/os/Binder.java5
-rw-r--r--core/java/android/os/Build.java5
-rw-r--r--core/java/android/os/Debug.java9
-rw-r--r--core/java/android/os/Environment.java30
-rw-r--r--core/java/android/os/FileUtils.java17
-rw-r--r--core/java/android/os/INetworkManagementService.aidl24
-rw-r--r--core/java/android/os/Looper.java1
-rw-r--r--core/java/android/os/MemoryFile.java7
-rw-r--r--core/java/android/os/MessageQueue.java3
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java80
-rw-r--r--core/java/android/os/PowerManager.java43
-rw-r--r--core/java/android/os/Process.java20
-rw-r--r--core/java/android/os/RecoverySystem.java7
-rw-r--r--core/java/android/os/StrictMode.java7
-rw-r--r--core/java/android/os/storage/StorageVolume.java24
-rw-r--r--core/java/android/pim/ICalendar.java3
-rw-r--r--core/java/android/pim/RecurrenceSet.java5
-rw-r--r--core/java/android/preference/CheckBoxPreference.java273
-rw-r--r--core/java/android/preference/MultiSelectListPreference.java6
-rw-r--r--core/java/android/preference/Preference.java17
-rw-r--r--core/java/android/preference/PreferenceActivity.java109
-rw-r--r--core/java/android/preference/PreferenceFragment.java5
-rw-r--r--core/java/android/preference/SwitchPreference.java180
-rw-r--r--core/java/android/preference/TwoStatePreference.java309
-rw-r--r--core/java/android/provider/BrowserContract.java7
-rw-r--r--core/java/android/provider/Calendar.java1307
-rw-r--r--core/java/android/provider/CallLog.java15
-rw-r--r--core/java/android/provider/ContactsContract.java221
-rw-r--r--core/java/android/provider/MediaStore.java10
-rw-r--r--core/java/android/provider/Settings.java92
-rw-r--r--core/java/android/provider/Telephony.java19
-rw-r--r--core/java/android/provider/VoicemailContract.java139
-rw-r--r--core/java/android/server/BluetoothA2dpService.java14
-rw-r--r--core/java/android/server/BluetoothAdapterProperties.java3
-rw-r--r--core/java/android/server/BluetoothBondState.java86
-rw-r--r--core/java/android/server/BluetoothInputProfileHandler.java26
-rw-r--r--core/java/android/server/BluetoothPanProfileHandler.java28
-rwxr-xr-xcore/java/android/server/BluetoothService.java129
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java4
-rw-r--r--core/java/android/speech/RecognitionListener.java3
-rw-r--r--core/java/android/speech/RecognizerIntent.java39
-rw-r--r--core/java/android/speech/SpeechRecognizer.java16
-rw-r--r--core/java/android/speech/srec/Recognizer.java1
-rw-r--r--core/java/android/speech/tts/AbstractSynthesisCallback.java34
-rw-r--r--core/java/android/speech/tts/AudioMessageParams.java38
-rw-r--r--core/java/android/speech/tts/AudioPlaybackHandler.java521
-rw-r--r--core/java/android/speech/tts/BlockingMediaPlayer.java146
-rw-r--r--core/java/android/speech/tts/FileSynthesisCallback.java250
-rwxr-xr-xcore/java/android/speech/tts/ITextToSpeechCallback.aidl (renamed from core/java/android/speech/tts/ITtsCallback.aidl)8
-rw-r--r--core/java/android/speech/tts/ITextToSpeechService.aidl140
-rwxr-xr-xcore/java/android/speech/tts/ITts.aidl69
-rw-r--r--core/java/android/speech/tts/MessageParams.java42
-rw-r--r--core/java/android/speech/tts/PlaybackSynthesisCallback.java221
-rw-r--r--core/java/android/speech/tts/SilenceMessageParams.java44
-rw-r--r--core/java/android/speech/tts/SynthesisCallback.java107
-rw-r--r--core/java/android/speech/tts/SynthesisMessageParams.java93
-rw-r--r--core/java/android/speech/tts/SynthesisRequest.java122
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java1451
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java859
-rw-r--r--core/java/android/speech/tts/TtsEngines.java200
-rw-r--r--core/java/android/text/BoringLayout.java44
-rw-r--r--core/java/android/text/CharSequenceIterator.java100
-rw-r--r--core/java/android/text/GraphicsOperations.java7
-rw-r--r--core/java/android/text/MeasuredText.java5
-rw-r--r--core/java/android/text/Selection.java46
-rw-r--r--core/java/android/text/SpannableStringBuilder.java84
-rw-r--r--core/java/android/text/StaticLayout.java2
-rw-r--r--core/java/android/text/TextLine.java98
-rw-r--r--core/java/android/text/TextUtils.java76
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java26
-rw-r--r--core/java/android/text/method/BaseMovementMethod.java22
-rw-r--r--core/java/android/text/method/WordIterator.java221
-rw-r--r--core/java/android/text/style/SuggestionSpan.aidl19
-rw-r--r--core/java/android/text/style/SuggestionSpan.java198
-rw-r--r--core/java/android/text/style/TextAppearanceSpan.java5
-rw-r--r--core/java/android/util/CalendarUtils.java348
-rw-r--r--core/java/android/util/Config.java60
-rw-r--r--core/java/android/util/FinitePool.java5
-rw-r--r--core/java/android/util/FloatProperty.java48
-rw-r--r--core/java/android/util/IntProperty.java48
-rw-r--r--core/java/android/util/JsonReader.java19
-rw-r--r--core/java/android/util/Log.java12
-rw-r--r--core/java/android/util/LruCache.java3
-rw-r--r--core/java/android/util/NoSuchPropertyException.java30
-rw-r--r--core/java/android/util/NtpTrustedTime.java97
-rw-r--r--core/java/android/util/Poolable.java2
-rw-r--r--core/java/android/util/Property.java106
-rw-r--r--core/java/android/util/ReflectiveProperty.java163
-rw-r--r--core/java/android/util/TimeUtils.java2
-rw-r--r--core/java/android/util/TrustedTime.java55
-rw-r--r--core/java/android/util/Xml.java40
-rw-r--r--core/java/android/view/GLES20Canvas.java179
-rw-r--r--core/java/android/view/GLES20Layer.java81
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java4
-rw-r--r--core/java/android/view/GLES20RenderLayer.java91
-rw-r--r--core/java/android/view/GLES20TextureLayer.java76
-rw-r--r--[-rwxr-xr-x]core/java/android/view/GestureDetector.java30
-rw-r--r--core/java/android/view/Gravity.java93
-rw-r--r--core/java/android/view/HardwareCanvas.java8
-rw-r--r--core/java/android/view/HardwareLayer.java12
-rw-r--r--core/java/android/view/HardwareRenderer.java276
-rwxr-xr-xcore/java/android/view/InputDevice.java10
-rwxr-xr-xcore/java/android/view/InputEvent.java46
-rw-r--r--core/java/android/view/InputEventConsistencyVerifier.java730
-rwxr-xr-xcore/java/android/view/KeyEvent.java101
-rw-r--r--core/java/android/view/LayoutInflater.java82
-rw-r--r--core/java/android/view/MenuItem.java104
-rw-r--r--core/java/android/view/MotionEvent.java1087
-rwxr-xr-xcore/java/android/view/OrientationEventListener.java3
-rw-r--r--core/java/android/view/ScaleGestureDetector.java23
-rw-r--r--core/java/android/view/SurfaceView.java5
-rw-r--r--core/java/android/view/TextureView.java334
-rw-r--r--core/java/android/view/VelocityTracker.java15
-rw-r--r--core/java/android/view/View.java1270
-rw-r--r--core/java/android/view/ViewAncestor.java (renamed from core/java/android/view/ViewRoot.java)1011
-rw-r--r--[-rwxr-xr-x]core/java/android/view/ViewConfiguration.java41
-rw-r--r--core/java/android/view/ViewDebug.java237
-rw-r--r--core/java/android/view/ViewGroup.java839
-rw-r--r--core/java/android/view/ViewParent.java19
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java96
-rw-r--r--core/java/android/view/WindowManager.java10
-rw-r--r--core/java/android/view/WindowManagerImpl.java39
-rw-r--r--core/java/android/view/WindowManagerPolicy.java10
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java3
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java635
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java158
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.aidl19
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java969
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java543
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl41
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl36
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl18
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java11
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java3
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java43
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java99
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java99
-rw-r--r--core/java/android/webkit/BrowserFrame.java56
-rw-r--r--core/java/android/webkit/CallbackProxy.java41
-rw-r--r--core/java/android/webkit/ClientCertRequestHandler.java78
-rw-r--r--core/java/android/webkit/DataLoader.java2
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java8
-rw-r--r--core/java/android/webkit/HTML5VideoInline.java49
-rw-r--r--core/java/android/webkit/HTML5VideoView.java12
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java30
-rw-r--r--core/java/android/webkit/JniUtil.java6
-rw-r--r--core/java/android/webkit/L10nUtils.java3
-rw-r--r--core/java/android/webkit/PluginFullScreenHolder.java136
-rw-r--r--core/java/android/webkit/SslCertLookupTable.java4
-rw-r--r--core/java/android/webkit/SslClientCertLookupTable.java73
-rw-r--r--core/java/android/webkit/ViewStateSerializer.java79
-rw-r--r--core/java/android/webkit/WebChromeClient.java13
-rw-r--r--core/java/android/webkit/WebSettings.java32
-rw-r--r--core/java/android/webkit/WebTextView.java26
-rw-r--r--core/java/android/webkit/WebView.java594
-rw-r--r--core/java/android/webkit/WebViewClient.java46
-rw-r--r--core/java/android/webkit/WebViewCore.java113
-rw-r--r--core/java/android/webkit/ZoomManager.java49
-rw-r--r--core/java/android/webkit/webdriver/By.java252
-rw-r--r--core/java/android/webkit/webdriver/WebDriver.java843
-rw-r--r--core/java/android/webkit/webdriver/WebDriverException.java38
-rw-r--r--core/java/android/webkit/webdriver/WebElement.java388
-rw-r--r--core/java/android/webkit/webdriver/WebElementNotFoundException.java41
-rw-r--r--core/java/android/webkit/webdriver/WebElementStaleException.java42
-rw-r--r--core/java/android/webkit/webdriver/WebViewClient.java125
-rw-r--r--core/java/android/webkit/webdriver/WebchromeClientWrapper.java193
-rw-r--r--core/java/android/widget/AbsListView.java31
-rw-r--r--core/java/android/widget/AbsSeekBar.java3
-rw-r--r--core/java/android/widget/AbsoluteLayout.java5
-rw-r--r--core/java/android/widget/AdapterView.java44
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java66
-rw-r--r--core/java/android/widget/CheckedTextView.java9
-rw-r--r--core/java/android/widget/CompoundButton.java19
-rw-r--r--core/java/android/widget/CursorAdapter.java3
-rw-r--r--core/java/android/widget/CursorTreeAdapter.java3
-rw-r--r--core/java/android/widget/DatePicker.java89
-rw-r--r--core/java/android/widget/ExpandableListView.java27
-rw-r--r--core/java/android/widget/FrameLayout.java26
-rw-r--r--core/java/android/widget/GridLayout.java2285
-rw-r--r--core/java/android/widget/GridView.java27
-rw-r--r--core/java/android/widget/ImageView.java39
-rw-r--r--core/java/android/widget/LinearLayout.java356
-rw-r--r--core/java/android/widget/ListView.java62
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java6
-rw-r--r--core/java/android/widget/PopupWindow.java8
-rw-r--r--core/java/android/widget/ProgressBar.java60
-rw-r--r--core/java/android/widget/RelativeLayout.java52
-rw-r--r--core/java/android/widget/RemoteViews.java106
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java21
-rw-r--r--core/java/android/widget/RemoteViewsService.java71
-rw-r--r--core/java/android/widget/ScrollView.java5
-rw-r--r--core/java/android/widget/SearchView.java5
-rw-r--r--core/java/android/widget/SimpleCursorAdapter.java14
-rw-r--r--core/java/android/widget/Space.java75
-rw-r--r--core/java/android/widget/StackView.java51
-rw-r--r--core/java/android/widget/Switch.java5
-rw-r--r--core/java/android/widget/TabWidget.java15
-rw-r--r--core/java/android/widget/TableRow.java3
-rw-r--r--core/java/android/widget/TextView.java1679
-rw-r--r--core/java/android/widget/TimePicker.java5
-rw-r--r--core/java/android/widget/ZoomButtonsController.java10
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java373
-rwxr-xr-xcore/java/com/android/internal/app/IMediaContainerService.aidl5
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java3
-rw-r--r--core/java/com/android/internal/content/NativeLibraryHelper.java3
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java11
-rw-r--r--core/java/com/android/internal/net/DomainNameValidator.java3
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java243
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java1
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java144
-rw-r--r--core/java/com/android/internal/os/SamplingProfilerIntegration.java57
-rw-r--r--core/java/com/android/internal/os/WrapperInit.java117
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java251
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java140
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl1
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl1
-rw-r--r--core/java/com/android/internal/util/Objects.java45
-rw-r--r--core/java/com/android/internal/util/Preconditions.java57
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java4
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java2
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl1
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl5
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java2
-rw-r--r--core/java/com/android/internal/view/StandaloneActionMode.java2
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItem.java27
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java29
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuPresenter.java476
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuView.java433
-rw-r--r--core/java/com/android/internal/view/menu/BaseMenuPresenter.java203
-rw-r--r--core/java/com/android/internal/view/menu/ExpandedMenuView.java25
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java6
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuPresenter.java141
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuView.java99
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java16
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuPresenter.java210
-rw-r--r--core/java/com/android/internal/view/menu/MenuBuilder.java780
-rw-r--r--core/java/com/android/internal/view/menu/MenuDialogHelper.java59
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java252
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java156
-rw-r--r--core/java/com/android/internal/view/menu/MenuPresenter.java128
-rw-r--r--core/java/com/android/internal/view/menu/MenuView.java14
-rw-r--r--core/java/com/android/internal/view/menu/SubMenuBuilder.java22
-rw-r--r--core/java/com/android/internal/widget/AbsActionBarView.java213
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java83
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java144
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java783
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java7
-rw-r--r--core/java/com/android/internal/widget/IRemoteViewsFactory.aidl2
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java19
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java8
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java41
-rw-r--r--core/java/com/android/internal/widget/ScrollingTabContainerView.java261
-rw-r--r--core/java/com/android/internal/widget/TextProgressBar.java3
-rw-r--r--core/java/com/google/android/mms/pdu/EncodedStringValue.java3
-rwxr-xr-xcore/java/com/google/android/mms/pdu/PduParser.java3
-rw-r--r--core/java/com/google/android/mms/pdu/PduPersister.java3
-rw-r--r--core/java/com/google/android/mms/util/AbstractCache.java3
-rw-r--r--core/java/com/google/android/mms/util/PduCache.java3
379 files changed, 32181 insertions, 8798 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 03346fe..8bb305d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -25,6 +25,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
* An accessibility service runs in the background and receives callbacks by the system
@@ -39,21 +40,67 @@ import android.view.accessibility.AccessibilityEvent;
* <p>
* <code>
* &lt;service android:name=".MyAccessibilityService"&gt;<br>
- * &lt;intent-filter&gt;<br>
- * &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;<br>
- * &lt;/intent-filter&gt;<br>
+ * &nbsp;&nbsp;&lt;intent-filter&gt;<br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;<br>
+ * &nbsp;&nbsp;&lt;/intent-filter&gt;<br>
* &lt;/service&gt;<br>
* </code>
+ * </p>
* <p>
* The lifecycle of an accessibility service is managed exclusively by the system. Starting
* or stopping an accessibility service is triggered by an explicit user action through
* enabling or disabling it in the device settings. After the system binds to a service it
* calls {@link AccessibilityService#onServiceConnected()}. This method can be
- * overriden by clients that want to perform post binding setup. An accessibility service
- * is configured though setting an {@link AccessibilityServiceInfo} by calling
- * {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. You can call this
- * method any time to change the service configuration but it is good practice to do that
- * in the overriden {@link AccessibilityService#onServiceConnected()}.
+ * overriden by clients that want to perform post binding setup.
+ * </p>
+ * <p>
+ * An accessibility service can be configured to receive specific types of accessibility events,
+ * listen only to specific packages, get events from each type only once in a given time frame,
+ * retrieve window content, specify a settings activity, etc.
+ * </p>
+ * There are two approaches for configuring an accessibility service:
+ * <ul>
+ * <li>
+ * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
+ * the service. A service declaration with a meta-data tag is presented below:
+ * <p>
+ * <code>
+ * &lt;service android:name=".MyAccessibilityService"&gt;<br>
+ * &nbsp;&nbsp;&lt;intent-filter&gt;<br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;<br>
+ * &nbsp;&nbsp;&lt;/intent-filter&gt;<br>
+ * &nbsp;&nbsp;&lt;meta-data android:name="android.accessibilityservice.as" android:resource="@xml/accessibilityservice" /&gt;<br>
+ * &lt;/service&gt;<br>
+ * </code>
+ * </p>
+ * <p>
+ * <strong>
+ * This approach enables setting all accessibility service properties.
+ * </strong>
+ * </p>
+ * <p>
+ * For more details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ * </li>
+ * <li>
+ * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note
+ * that this method can be called any time to change the service configuration.<br>
+ * <p>
+ * <strong>
+ * This approach enables setting only dynamically configurable accessibility
+ * service properties:
+ * {@link AccessibilityServiceInfo#eventTypes},
+ * {@link AccessibilityServiceInfo#feedbackType},
+ * {@link AccessibilityServiceInfo#flags},
+ * {@link AccessibilityServiceInfo#notificationTimeout},
+ * {@link AccessibilityServiceInfo#packageNames}
+ * </strong>
+ * </p>
+ * <p>
+ * For more details refer to {@link AccessibilityServiceInfo}.
+ * </p>
+ * </li>
+ * </ul>
* <p>
* An accessibility service can be registered for events in specific packages to provide a
* specific type of feedback and is notified with a certain timeout after the last event
@@ -105,6 +152,62 @@ public abstract class AccessibilityService extends Service {
public static final String SERVICE_INTERFACE =
"android.accessibilityservice.AccessibilityService";
+ /**
+ * Name under which an AccessibilityService component publishes information
+ * about itself. This meta-data must reference an XML resource containing
+ * an
+ * <code>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</code>
+ * tag. This is a a sample XML file configuring an accessibility service:
+ * <p>
+ * <code>
+ * &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
+ * &lt;accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"<br>
+ * &nbsp;&nbsp;android:accessibilityEventTypes="typeViewClicked|typeViewFocused"<br>
+ * &nbsp;&nbsp;android:packageNames="foo.bar, foo.baz"<br>
+ * &nbsp;&nbsp;android:accessibilityFeedbackType="feedbackSpoken"<br>
+ * &nbsp;&nbsp;android:notificationTimeout="100"<br>
+ * &nbsp;&nbsp;android:accessibilityFlags="flagDefault"<br>
+ * &nbsp;&nbsp;android:settingsActivity="foo.bar.TestBackActivity"<br>
+ * &nbsp;&nbsp;. . .<br>
+ * /&gt;
+ * </code>
+ * </p>
+ * <p>
+ * <strong>Note:</strong> A service can retrieve only the content of the active window.
+ * An active window is the source of the most recent event of type
+ * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START},
+ * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END},
+ * {@link AccessibilityEvent#TYPE_VIEW_CLICKED},
+ * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED},
+ * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
+ * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT},
+ * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED},
+ * {@link AccessibilityEvent#TYPE_VIEW_SELECTED},
+ * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED},
+ * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}.
+ * Therefore the service should:
+ * <ul>
+ * <li>
+ * Register for all event types with no notification timeout and keep track
+ * for the active window by calling
+ * {@link AccessibilityEvent#getAccessibilityWindowId()} of the last received
+ * event and compare this with the
+ * {@link AccessibilityNodeInfo#getAccessibilityWindowId()} before calling
+ * retrieval methods on the latter.
+ * </li>
+ * <li>
+ * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail
+ * since the active window has changed and the service did not get the
+ * accessibility event. Note that it is possible to have a retrieval method
+ * failing event adopting the strategy specified in the previous bullet
+ * because the accessibility event dispatch is asynchronous and crosses
+ * process boundaries.
+ * </li>
+ * <ul>
+ * </p>
+ */
+ public static final String SERVICE_META_DATA = "android.accessibilityservice";
+
private static final String LOG_TAG = "AccessibilityService";
private AccessibilityServiceInfo mInfo;
@@ -165,7 +268,7 @@ public abstract class AccessibilityService extends Service {
/**
* Implement to return the implementation of the internal accessibility
- * service interface. Subclasses should not override.
+ * service interface.
*/
@Override
public final IBinder onBind(Intent intent) {
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index bf9e07d..b9878cd 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -16,8 +16,25 @@
package android.accessibilityservice;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.accessibility.AccessibilityEvent;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
/**
* This class describes an {@link AccessibilityService}. The system
@@ -30,6 +47,8 @@ import android.os.Parcelable;
*/
public class AccessibilityServiceInfo implements Parcelable {
+ private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
+
/**
* Denotes spoken feedback.
*/
@@ -64,7 +83,9 @@ public class AccessibilityServiceInfo implements Parcelable {
/**
* The event types an {@link AccessibilityService} is interested in.
- *
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
@@ -77,13 +98,18 @@ public class AccessibilityServiceInfo implements Parcelable {
/**
* The package names an {@link AccessibilityService} is interested in. Setting
- * to null is equivalent to all packages.
+ * to null is equivalent to all packages.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
*/
public String[] packageNames;
/**
* The feedback type an {@link AccessibilityService} provides.
- *
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
* @see #FEEDBACK_AUDIBLE
* @see #FEEDBACK_GENERIC
* @see #FEEDBACK_HAPTIC
@@ -96,6 +122,9 @@ public class AccessibilityServiceInfo implements Parcelable {
* The timeout after the most recent event of a given type before an
* {@link AccessibilityService} is notified.
* <p>
+ * <strong>Can be dynamically set at runtime.</strong>.
+ * </p>
+ * <p>
* Note: The event notification timeout is useful to avoid propagating events to the client
* too frequently since this is accomplished via an expensive interprocess call.
* One can think of the timeout as a criteria to determine when event generation has
@@ -106,11 +135,181 @@ public class AccessibilityServiceInfo implements Parcelable {
/**
* This field represents a set of flags used for configuring an
* {@link AccessibilityService}.
- *
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
* @see #DEFAULT
*/
public int flags;
+ /**
+ * The unique string Id to identify the accessibility service.
+ */
+ private String mId;
+
+ /**
+ * The Service that implements this accessibility service component.
+ */
+ private ResolveInfo mResolveInfo;
+
+ /**
+ * The accessibility service setting activity's name, used by the system
+ * settings to launch the setting activity of this accessibility service.
+ */
+ private String mSettingsActivityName;
+
+ /**
+ * Flag whether this accessibility service can retrieve screen content.
+ */
+ private boolean mCanRetrieveWindowContent;
+
+ /**
+ * Creates a new instance.
+ */
+ public AccessibilityServiceInfo() {
+ /* do nothing */
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param resolveInfo The service resolve info.
+ * @param context Context for accessing resources.
+ * @throws XmlPullParserException If a XML parsing error occurs.
+ * @throws IOException If a XML parsing error occurs.
+ *
+ * @hide
+ */
+ public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
+ throws XmlPullParserException, IOException {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
+ mResolveInfo = resolveInfo;
+
+ String settingsActivityName = null;
+ boolean retrieveScreenContent = false;
+ XmlResourceParser parser = null;
+
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ parser = serviceInfo.loadXmlMetaData(packageManager,
+ AccessibilityService.SERVICE_META_DATA);
+ if (parser == null) {
+ return;
+ }
+
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ String nodeName = parser.getName();
+ if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
+ throw new XmlPullParserException( "Meta-data does not start with"
+ + TAG_ACCESSIBILITY_SERVICE + " tag");
+ }
+
+ AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ Resources resources = packageManager.getResourcesForApplication(
+ serviceInfo.applicationInfo);
+ TypedArray asAttributes = resources.obtainAttributes(allAttributes,
+ com.android.internal.R.styleable.AccessibilityService);
+ eventTypes = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
+ 0);
+ String packageNamez = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityService_packageNames);
+ if (packageNamez != null) {
+ packageNames = packageNamez.split("(\\s)*,(\\s)*");
+ }
+ feedbackType = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
+ 0);
+ notificationTimeout = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
+ 0);
+ flags = asAttributes.getInt(
+ 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);
+ asAttributes.recycle();
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException( "Unable to create context for: "
+ + serviceInfo.packageName);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ /**
+ * Updates the properties that an AccessibilitySerivice can change dynamically.
+ *
+ * @param other The info from which to update the properties.
+ *
+ * @hide
+ */
+ public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
+ eventTypes = other.eventTypes;
+ packageNames = other.packageNames;
+ feedbackType = other.feedbackType;
+ notificationTimeout = other.notificationTimeout;
+ flags = other.flags;
+ }
+
+ /**
+ * The accessibility service id.
+ * <p>
+ * <strong>Generated by the system.</strong>
+ * </p>
+ * @return The id.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * The service {@link ResolveInfo}.
+ * <p>
+ * <strong>Generated by the system.</strong>
+ * </p>
+ * @return The info.
+ */
+ public ResolveInfo getResolveInfo() {
+ return mResolveInfo;
+ }
+
+ /**
+ * The settings activity name.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The settings activity name.
+ */
+ public String getSettingsActivityName() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Whether this service can retrieve the currently focused window content.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return True screen content is retrieved.
+ */
+ public boolean getCanRetrieveWindowContent() {
+ return mCanRetrieveWindowContent;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public int describeContents() {
return 0;
}
@@ -121,6 +320,142 @@ public class AccessibilityServiceInfo implements Parcelable {
parcel.writeInt(feedbackType);
parcel.writeLong(notificationTimeout);
parcel.writeInt(flags);
+ parcel.writeString(mId);
+ parcel.writeParcelable(mResolveInfo, 0);
+ parcel.writeString(mSettingsActivityName);
+ parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ eventTypes = parcel.readInt();
+ packageNames = parcel.readStringArray();
+ feedbackType = parcel.readInt();
+ notificationTimeout = parcel.readLong();
+ flags = parcel.readInt();
+ mId = parcel.readString();
+ mResolveInfo = parcel.readParcelable(null);
+ mSettingsActivityName = parcel.readString();
+ mCanRetrieveWindowContent = (parcel.readInt() == 1);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ appendEventTypes(stringBuilder, eventTypes);
+ stringBuilder.append(", ");
+ appendPackageNames(stringBuilder, packageNames);
+ stringBuilder.append(", ");
+ appendFeedbackTypes(stringBuilder, feedbackType);
+ stringBuilder.append(", ");
+ stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
+ stringBuilder.append(", ");
+ appendFlags(stringBuilder, flags);
+ stringBuilder.append(", ");
+ stringBuilder.append("id: ").append(mId);
+ stringBuilder.append(", ");
+ stringBuilder.append("resolveInfo: ").append(mResolveInfo);
+ stringBuilder.append(", ");
+ stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
+ stringBuilder.append(", ");
+ stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
+ return stringBuilder.toString();
+ }
+
+ private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
+ stringBuilder.append("feedbackTypes:");
+ stringBuilder.append("[");
+ while (feedbackTypes != 0) {
+ final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
+ stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
+ feedbackTypes &= ~feedbackTypeBit;
+ if (feedbackTypes != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
+ stringBuilder.append("packageNames:");
+ stringBuilder.append("[");
+ if (packageNames != null) {
+ final int packageNameCount = packageNames.length;
+ for (int i = 0; i < packageNameCount; i++) {
+ stringBuilder.append(packageNames[i]);
+ if (i < packageNameCount - 1) {
+ stringBuilder.append(", ");
+ }
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
+ stringBuilder.append("eventTypes:");
+ stringBuilder.append("[");
+ while (eventTypes != 0) {
+ final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
+ stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
+ eventTypes &= ~eventTypeBit;
+ if (eventTypes != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendFlags(StringBuilder stringBuilder, int flags) {
+ stringBuilder.append("flags:");
+ stringBuilder.append("[");
+ while (flags != 0) {
+ final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
+ stringBuilder.append(flagToString(flagBit));
+ flags &= ~flagBit;
+ if (flags != 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.
+ *
+ * @param feedbackType The feedback type.
+ * @return The string representation.
+ */
+ public static String feedbackTypeToString(int feedbackType) {
+ switch (feedbackType) {
+ case FEEDBACK_AUDIBLE:
+ return "FEEDBACK_AUDIBLE";
+ case FEEDBACK_HAPTIC:
+ return "FEEDBACK_HAPTIC";
+ case FEEDBACK_GENERIC:
+ return "FEEDBACK_GENERIC";
+ case FEEDBACK_SPOKEN:
+ return "FEEDBACK_SPOKEN";
+ case FEEDBACK_VISUAL:
+ return "FEEDBACK_VISUAL";
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns the string representation of a flag. For example,
+ * {@link #DEFAULT} is represented by the string DEFAULT.
+ *
+ * @param flag The flag.
+ * @return The string representation.
+ */
+ public static String flagToString(int flag) {
+ switch (flag) {
+ case DEFAULT:
+ return "DEFAULT";
+ default:
+ return null;
+ }
}
/**
@@ -130,11 +465,7 @@ public class AccessibilityServiceInfo implements Parcelable {
new Parcelable.Creator<AccessibilityServiceInfo>() {
public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.eventTypes = parcel.readInt();
- info.packageNames = parcel.readStringArray();
- info.feedbackType = parcel.readInt();
- info.notificationTimeout = parcel.readLong();
- info.flags = parcel.readInt();
+ info.initFromParcel(parcel);
return info;
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 7157def..19f0bf0 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -17,14 +17,72 @@
package android.accessibilityservice;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
- * Interface AccessibilityManagerService#Service implements, and passes to an
- * AccessibilityService so it can dynamically configure how the system handles it.
+ * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
*
* @hide
*/
-oneway interface IAccessibilityServiceConnection {
+interface IAccessibilityServiceConnection {
void setServiceInfo(in AccessibilityServiceInfo info);
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ *
+ * @param accessibilityWindowId A unique window id.
+ * @param accessibilityViewId A unique View accessibility id.
+ * @return The node info.
+ */
+ AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+ int accessibilityViewId);
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
+ * insensitive containment.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received infos by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ *
+ * @param text The searched text.
+ * @return A list of node info.
+ */
+ List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text);
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by View id.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ *
+ * @param id The id of the node.
+ * @return The node info.
+ */
+ AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId);
+
+ /**
+ * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+ *
+ * @param accessibilityWindowId The id of the window.
+ * @param accessibilityViewId The of a view in the .
+ * @return Whether the action was performed.
+ */
+ boolean performAccessibilityAction(int accessibilityWindowId, int accessibilityViewId,
+ int action);
}
diff --git a/core/java/android/accounts/AccountAuthenticatorActivity.java b/core/java/android/accounts/AccountAuthenticatorActivity.java
index 5cce6da..6a55ddf 100644
--- a/core/java/android/accounts/AccountAuthenticatorActivity.java
+++ b/core/java/android/accounts/AccountAuthenticatorActivity.java
@@ -26,7 +26,7 @@ import android.os.Bundle;
* to handle the request then it can have the activity extend AccountAuthenticatorActivity.
* The AbstractAccountAuthenticator passes in the response to the intent using the following:
* <pre>
- * intent.putExtra(Constants.ACCOUNT_AUTHENTICATOR_RESPONSE_KEY, response);
+ * intent.putExtra({@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}, response);
* </pre>
* The activity then sets the result that is to be handed to the response via
* {@link #setAccountAuthenticatorResult(android.os.Bundle)}.
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 5bdc79d..2156425 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -179,6 +179,7 @@ public class AccountManager {
public static final String KEY_PASSWORD = "password";
public static final String KEY_ACCOUNTS = "accounts";
+
public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -1269,7 +1270,7 @@ public class AccountManager {
/** Handles the responses from the AccountManager */
private class Response extends IAccountManagerResponse.Stub {
public void onResult(Bundle bundle) {
- Intent intent = bundle.getParcelable("intent");
+ Intent intent = bundle.getParcelable(KEY_INTENT);
if (intent != null && mActivity != null) {
// since the user provided an Activity we will silently start intents
// that we see
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 93983a6..20d5b96 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -37,6 +37,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.RegisteredServicesCacheListener;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
@@ -73,8 +74,7 @@ import java.util.concurrent.atomic.AtomicReference;
* 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)context.getSystemService(Context.ACCOUNT_SERVICE)
+ * AccountManager accountManager = AccountManager.get(context);
* @hide
*/
public class AccountManagerService
@@ -1064,14 +1064,18 @@ public class AccountManagerService
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("unknown account type: " + accountType);
}
- return authContext.getString(serviceInfo.type.labelId);
+ try {
+ return authContext.getString(serviceInfo.type.labelId);
+ } catch (Resources.NotFoundException e) {
+ throw new IllegalArgumentException("unknown account type: " + accountType);
+ }
}
private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
- // See FLAT_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
+ // 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);
diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
index 293df78..bfbae24 100644
--- a/core/java/android/accounts/ChooseAccountActivity.java
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -18,6 +18,7 @@ package android.accounts;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
@@ -103,7 +104,12 @@ public class ChooseAccountActivity extends Activity {
} catch (PackageManager.NameNotFoundException e) {
// Nothing we can do much here, just log
if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "No icon for account type " + accountType);
+ Log.w(TAG, "No icon name for account type " + accountType);
+ }
+ } catch (Resources.NotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon resource for account type " + accountType);
}
}
}
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 89eee6d..0ee683c 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -58,6 +58,12 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ // we were somehow started with bad parameters. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
// Grant 'account'/'type' to mUID
mAccount = extras.getParcelable(EXTRAS_ACCOUNT);
@@ -73,8 +79,15 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
return;
}
- final String accountTypeLabel = accountManagerService.getAccountLabel(mAccount.type);
-
+ String accountTypeLabel;
+ try {
+ accountTypeLabel = accountManagerService.getAccountLabel(mAccount.type);
+ } catch (IllegalArgumentException e) {
+ // label or resource was missing. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
final TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type);
authTokenTypeView.setVisibility(View.GONE);
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index bcab66e..ed4036d 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -31,11 +31,11 @@ import java.io.IOException;
import java.util.ArrayList;
/**
- * This class is used to instantiate menu XML files into Animator objects.
+ * This class is used to instantiate animator XML files into Animator objects.
* <p>
- * For performance reasons, menu inflation relies heavily on pre-processing of
+ * For performance reasons, inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
- * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
+ * to use this inflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource (R.
* <em>something</em> file.)
*/
diff --git a/core/java/android/animation/FloatEvaluator.java b/core/java/android/animation/FloatEvaluator.java
index 9e2054d..9463aa1 100644
--- a/core/java/android/animation/FloatEvaluator.java
+++ b/core/java/android/animation/FloatEvaluator.java
@@ -19,7 +19,7 @@ package android.animation;
/**
* This evaluator can be used to perform type interpolation between <code>float</code> values.
*/
-public class FloatEvaluator implements TypeEvaluator {
+public class FloatEvaluator implements TypeEvaluator<Number> {
/**
* This function returns the result of linearly interpolating the start and end values, with
@@ -35,8 +35,8 @@ public class FloatEvaluator implements TypeEvaluator {
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
- public Object evaluate(float fraction, Object startValue, Object endValue) {
- float startFloat = ((Number) startValue).floatValue();
- return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
+ public Float evaluate(float fraction, Number startValue, Number endValue) {
+ float startFloat = startValue.floatValue();
+ return startFloat + fraction * (endValue.floatValue() - startFloat);
}
} \ No newline at end of file
diff --git a/core/java/android/animation/IntEvaluator.java b/core/java/android/animation/IntEvaluator.java
index 7288927..34fb0dc 100644
--- a/core/java/android/animation/IntEvaluator.java
+++ b/core/java/android/animation/IntEvaluator.java
@@ -19,7 +19,7 @@ package android.animation;
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
-public class IntEvaluator implements TypeEvaluator {
+public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
@@ -35,8 +35,8 @@ public class IntEvaluator implements TypeEvaluator {
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
- public Object evaluate(float fraction, Object startValue, Object endValue) {
- int startInt = ((Number) startValue).intValue();
- return (int) (startInt + fraction * (((Number) endValue).intValue() - startInt));
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ int startInt = startValue;
+ return (int)(startInt + fraction * (endValue - startInt));
}
} \ No newline at end of file
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 22dd3c7..d25de97 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -18,6 +18,7 @@ package android.animation;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
@@ -48,6 +49,18 @@ import java.util.List;
* with setting up the basic properties of the animations used in these four situations,
* or with setting up custom animations for any or all of the four.</p>
*
+ * <p>By default, the DISAPPEARING animation begins immediately, as does the CHANGE_APPEARING
+ * animation. The other animations begin after a delay that is set to the default duration
+ * of the animations. This behavior facilitates a sequence of animations in transitions as
+ * follows: when an item is being added to a layout, the other children of that container will
+ * move first (thus creating space for the new item), then the appearing animation will run to
+ * animate the item being added. Conversely, when an item is removed from a container, the
+ * animation to remove it will run first, then the animations of the other children in the
+ * layout will run (closing the gap created in the layout when the item was removed). If this
+ * default choreography behavior is not desired, the {@link #setDuration(int, long)} and
+ * {@link #setStartDelay(int, long)} of any or all of the animations can be changed as
+ * appropriate.</p>
+ *
* <p>The animations specified for the transition, both the defaults and any custom animations
* set on the transition object, are templates only. That is, these animations exist to hold the
* basic animation properties, such as the duration, start delay, and properties being animated.
@@ -58,8 +71,9 @@ import java.util.List;
* moving as a result of the layout event) as well as the values that are changing (such as the
* position and size of that object). The actual values that are pushed to each animation
* depends on what properties are specified for the animation. For example, the default
- * CHANGE_APPEARING animation animates <code>left</code>, <code>top</code>, <code>right</code>,
- * and <code>bottom</code>. Values for these properties are updated with the pre- and post-layout
+ * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>,
+ * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties.
+ * Values for these properties are updated with the pre- and post-layout
* values when the transition begins. Custom animations will be similarly populated with
* the target and values being animated, assuming they use ObjectAnimator objects with
* property names that are known on the target object.</p>
@@ -198,6 +212,14 @@ public class LayoutTransition {
*/
private ArrayList<TransitionListener> mListeners;
+ /**
+ * Controls whether changing animations automatically animate the parent hierarchy as well.
+ * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
+ * transition begins, causing visual glitches and clipping.
+ * Default value is true.
+ */
+ private boolean mAnimateParentHierarchy = true;
+
/**
* Constructs a LayoutTransition object. By default, the object will listen to layout
@@ -211,14 +233,17 @@ public class LayoutTransition {
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
+ PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
+ PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder(this,
- pvhLeft, pvhTop, pvhRight, pvhBottom);
+ pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
defaultChangeIn.setDuration(DEFAULT_DURATION);
defaultChangeIn.setStartDelay(mChangingAppearingDelay);
defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
defaultChangeOut = defaultChangeIn.clone();
defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
+
defaultFadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f);
defaultFadeIn.setDuration(DEFAULT_DURATION);
defaultFadeIn.setStartDelay(mAppearingDelay);
@@ -560,122 +585,24 @@ public class LayoutTransition {
// only animate the views not being added or removed
if (child != newView) {
-
-
- // Make a copy of the appropriate animation
- final Animator anim = baseAnimator.clone();
-
- // Set the target object for the animation
- anim.setTarget(child);
-
- // A ObjectAnimator (or AnimatorSet of them) can extract start values from
- // its target object
- anim.setupStartValues();
-
- // If there's an animation running on this view already, cancel it
- Animator currentAnimation = pendingAnimations.get(child);
- if (currentAnimation != null) {
- currentAnimation.cancel();
- pendingAnimations.remove(child);
+ setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
+ }
+ }
+ if (mAnimateParentHierarchy) {
+ ViewGroup tempParent = parent;
+ while (tempParent != null) {
+ ViewParent parentParent = tempParent.getParent();
+ if (parentParent instanceof ViewGroup) {
+ setupChangeAnimation((ViewGroup)parentParent, changeReason, baseAnimator,
+ duration, tempParent);
+ tempParent = (ViewGroup) parentParent;
+ } else {
+ tempParent = null;
}
- // Cache the animation in case we need to cancel it later
- pendingAnimations.put(child, anim);
-
- // For the animations which don't get started, we have to have a means of
- // removing them from the cache, lest we leak them and their target objects.
- // We run an animator for the default duration+100 (an arbitrary time, but one
- // which should far surpass the delay between setting them up here and
- // handling layout events which start them.
- ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
- setDuration(duration+100);
- pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- pendingAnimations.remove(child);
- }
- });
- pendingAnimRemover.start();
-
- // Add a listener to track layout changes on this view. If we don't get a callback,
- // then there's nothing to animate.
- final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
-
- // Tell the animation to extract end values from the changed object
- anim.setupEndValues();
-
- long startDelay;
- if (changeReason == APPEARING) {
- startDelay = mChangingAppearingDelay + staggerDelay;
- staggerDelay += mChangingAppearingStagger;
- } else {
- startDelay = mChangingDisappearingDelay + staggerDelay;
- staggerDelay += mChangingDisappearingStagger;
- }
- anim.setStartDelay(startDelay);
- anim.setDuration(duration);
- Animator prevAnimation = currentChangingAnimations.get(child);
- if (prevAnimation != null) {
- prevAnimation.cancel();
- }
- Animator pendingAnimation = pendingAnimations.get(child);
- if (pendingAnimation != null) {
- pendingAnimations.remove(child);
- }
- // Cache the animation in case we need to cancel it later
- currentChangingAnimations.put(child, anim);
-
- if (anim instanceof ObjectAnimator) {
- ((ObjectAnimator) anim).setCurrentPlayTime(0);
- }
- anim.start();
-
- // this only removes listeners whose views changed - must clear the
- // other listeners later
- child.removeOnLayoutChangeListener(this);
- layoutChangeListenerMap.remove(child);
- }
- };
- // Remove the animation from the cache when it ends
- anim.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationStart(Animator animator) {
- if (mListeners != null) {
- for (TransitionListener listener : mListeners) {
- listener.startTransition(LayoutTransition.this, parent, child,
- changeReason == APPEARING ?
- CHANGE_APPEARING : CHANGE_DISAPPEARING);
- }
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animator) {
- child.removeOnLayoutChangeListener(listener);
- layoutChangeListenerMap.remove(child);
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- currentChangingAnimations.remove(child);
- if (mListeners != null) {
- for (TransitionListener listener : mListeners) {
- listener.endTransition(LayoutTransition.this, parent, child,
- changeReason == APPEARING ?
- CHANGE_APPEARING : CHANGE_DISAPPEARING);
- }
- }
- }
- });
-
- child.addOnLayoutChangeListener(listener);
- // cache the listener for later removal
- layoutChangeListenerMap.put(child, listener);
}
}
+
// This is the cleanup step. When we get this rendering event, we know that all of
// the appropriate animations have been set up and run. Now we can clear out the
// layout listeners.
@@ -694,6 +621,175 @@ public class LayoutTransition {
}
/**
+ * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
+ * cause the same changing animation to be run on the parent hierarchy as well. This allows
+ * containers of transitioning views to also transition, which may be necessary in situations
+ * where the containers bounds change between the before/after states and may clip their
+ * children during the transition animations. For example, layouts with wrap_content will
+ * adjust their bounds according to the dimensions of their children.
+ *
+ * @param animateParentHierarchy A boolean value indicating whether the parents of
+ * transitioning views should also be animated during the transition. Default value is true.
+ */
+ public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
+ mAnimateParentHierarchy = animateParentHierarchy;
+ }
+
+ /**
+ * Utility function called by runChangingTransition for both the children and the parent
+ * hierarchy.
+ */
+ private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
+ Animator baseAnimator, final long duration, final View child) {
+ // Make a copy of the appropriate animation
+ final Animator anim = baseAnimator.clone();
+
+ // Set the target object for the animation
+ anim.setTarget(child);
+
+ // A ObjectAnimator (or AnimatorSet of them) can extract start values from
+ // its target object
+ anim.setupStartValues();
+
+ // If there's an animation running on this view already, cancel it
+ Animator currentAnimation = pendingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ pendingAnimations.put(child, anim);
+
+ // For the animations which don't get started, we have to have a means of
+ // removing them from the cache, lest we leak them and their target objects.
+ // We run an animator for the default duration+100 (an arbitrary time, but one
+ // which should far surpass the delay between setting them up here and
+ // handling layout events which start them.
+ ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
+ setDuration(duration + 100);
+ pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ pendingAnimations.remove(child);
+ }
+ });
+ pendingAnimRemover.start();
+
+ // Add a listener to track layout changes on this view. If we don't get a callback,
+ // then there's nothing to animate.
+ final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+
+ // Tell the animation to extract end values from the changed object
+ anim.setupEndValues();
+ if (anim instanceof ValueAnimator) {
+ boolean valuesDiffer = false;
+ ValueAnimator valueAnim = (ValueAnimator)anim;
+ PropertyValuesHolder[] oldValues = valueAnim.getValues();
+ for (int i = 0; i < oldValues.length; ++i) {
+ PropertyValuesHolder pvh = oldValues[i];
+ KeyframeSet keyframeSet = pvh.mKeyframeSet;
+ if (keyframeSet.mFirstKeyframe == null ||
+ keyframeSet.mLastKeyframe == null ||
+ !keyframeSet.mFirstKeyframe.getValue().equals(
+ keyframeSet.mLastKeyframe.getValue())) {
+ valuesDiffer = true;
+ }
+ }
+ if (!valuesDiffer) {
+ return;
+ }
+ }
+
+ long startDelay;
+ if (changeReason == APPEARING) {
+ startDelay = mChangingAppearingDelay + staggerDelay;
+ staggerDelay += mChangingAppearingStagger;
+ } else {
+ startDelay = mChangingDisappearingDelay + staggerDelay;
+ staggerDelay += mChangingDisappearingStagger;
+ }
+ anim.setStartDelay(startDelay);
+ anim.setDuration(duration);
+
+ Animator prevAnimation = currentChangingAnimations.get(child);
+ if (prevAnimation != null) {
+ prevAnimation.cancel();
+ }
+ Animator pendingAnimation = pendingAnimations.get(child);
+ if (pendingAnimation != null) {
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ currentChangingAnimations.put(child, anim);
+
+ parent.requestTransitionStart(LayoutTransition.this);
+
+ // this only removes listeners whose views changed - must clear the
+ // other listeners later
+ child.removeOnLayoutChangeListener(this);
+ layoutChangeListenerMap.remove(child);
+ }
+ };
+ // Remove the animation from the cache when it ends
+ anim.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.startTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : CHANGE_DISAPPEARING);
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ child.removeOnLayoutChangeListener(listener);
+ layoutChangeListenerMap.remove(child);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ currentChangingAnimations.remove(child);
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.endTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : CHANGE_DISAPPEARING);
+ }
+ }
+ }
+ });
+
+ child.addOnLayoutChangeListener(listener);
+ // cache the listener for later removal
+ layoutChangeListenerMap.put(child, listener);
+ }
+
+ /**
+ * Starts the animations set up for a CHANGING transition. We separate the setup of these
+ * animations from actually starting them, to avoid side-effects that starting the animations
+ * may have on the properties of the affected objects. After setup, we tell the affected parent
+ * that this transition should be started. The parent informs its ViewAncestor, which then
+ * starts the transition after the current layout/measurement phase, just prior to drawing
+ * the view hierarchy.
+ *
+ * @hide
+ */
+ public void startChangingAnimations() {
+ for (Animator anim : currentChangingAnimations.values()) {
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ anim.start();
+ }
+ }
+
+ /**
* Returns true if animations are running which animate layout-related properties. This
* essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
* are running, since these animations operate on layout-related properties.
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index b8a7cb2..31c5f8d 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -17,6 +17,7 @@
package android.animation;
import android.util.Log;
+import android.util.Property;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -39,6 +40,8 @@ public final class ObjectAnimator extends ValueAnimator {
private String mPropertyName;
+ private Property mProperty;
+
/**
* 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.
@@ -63,7 +66,7 @@ public final class ObjectAnimator extends ValueAnimator {
* using more than one PropertyValuesHolder objects, then setting the propertyName simply
* sets the propertyName in the first of those PropertyValuesHolder objects.</p>
*
- * @param propertyName The name of the property being animated.
+ * @param propertyName The name of the property being animated. Should not be null.
*/
public void setPropertyName(String propertyName) {
// mValues could be null if this is being constructed piecemeal. Just record the
@@ -81,6 +84,31 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Sets the property that will be animated. Property objects will take precedence over
+ * properties specified by the {@link #setPropertyName(String)} method. Animations should
+ * be set up to use one or the other, not both.
+ *
+ * @param property The property being animated. Should not be null.
+ */
+ public void setProperty(Property property) {
+ // mValues could be null if this is being constructed piecemeal. Just record the
+ // propertyName to be used later when setValues() is called if so.
+ if (mValues != null) {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ String oldName = valuesHolder.getPropertyName();
+ valuesHolder.setProperty(property);
+ mValuesMap.remove(oldName);
+ mValuesMap.put(mPropertyName, valuesHolder);
+ }
+ if (mProperty != null) {
+ mPropertyName = property.getName();
+ }
+ mProperty = property;
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
* Gets the name of the property that will be animated. This name will be used to derive
* a setter function that will be called to set animated values.
* For example, a property name of <code>foo</code> will result
@@ -93,36 +121,6 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
- * Determine the setter or getter function using the JavaBeans convention of setFoo or
- * getFoo for a property named 'foo'. This function figures out what the name of the
- * function should be and uses reflection to find the Method with that name on the
- * target object.
- *
- * @param prefix "set" or "get", depending on whether we need a setter or getter.
- * @return Method the method associated with mPropertyName.
- */
- private Method getPropertyFunction(String prefix, Class valueType) {
- // TODO: faster implementation...
- Method returnVal = null;
- String firstLetter = mPropertyName.substring(0, 1);
- String theRest = mPropertyName.substring(1);
- firstLetter = firstLetter.toUpperCase();
- String setterName = prefix + firstLetter + theRest;
- Class args[] = null;
- if (valueType != null) {
- args = new Class[1];
- args[0] = valueType;
- }
- try {
- returnVal = mTarget.getClass().getMethod(setterName, args);
- } catch (NoSuchMethodException e) {
- Log.e("ObjectAnimator",
- "Couldn't find setter/getter for property " + mPropertyName + ": " + e);
- }
- return returnVal;
- }
-
- /**
* Creates a new ObjectAnimator object. This default constructor is primarily for
* use internally; the other constructors which take parameters are more generally
* useful.
@@ -131,8 +129,8 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
- * A constructor that takes a single property name and set of values. This constructor is
- * used in the simple case of animating a single property.
+ * Private utility constructor that initializes the target object and name of the
+ * property being animated.
*
* @param target The object whose property is to be animated. This object should
* have a public method on it called <code>setName()</code>, where <code>name</code> is
@@ -145,19 +143,29 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Private utility constructor that initializes the target object and property being animated.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ */
+ private <T> ObjectAnimator(T target, Property<T, ?> property) {
+ mTarget = target;
+ setProperty(property);
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between int values. A single
- * value implies that that value is the one being animated to. However, this is not typically
- * useful in a ValueAnimator object because there is no way for the object to determine the
- * starting value for the animation (unlike ObjectAnimator, which can derive that value
- * from the target object and property being animated). Therefore, there should typically
- * be two or more values.
+ * value implies that that value is the one being animated to. Two values imply a starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
*
* @param target The object whose property is to be animated. This object should
* have a public method on it called <code>setName()</code>, where <code>name</code> is
* the value of the <code>propertyName</code> parameter.
* @param propertyName The name of the property being animated.
* @param values A set of values that the animation will animate between over time.
- * @return A ValueAnimator object that is set up to animate between the given values.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
*/
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
@@ -166,19 +174,36 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Constructs and returns an ObjectAnimator that animates between int values. A single
+ * value implies that that value is the one being animated to. Two values imply a starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> property, int... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setIntValues(values);
+ return anim;
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between float values. A single
- * value implies that that value is the one being animated to. However, this is not typically
- * useful in a ValueAnimator object because there is no way for the object to determine the
- * starting value for the animation (unlike ObjectAnimator, which can derive that value
- * from the target object and property being animated). Therefore, there should typically
- * be two or more values.
+ * value implies that that value is the one being animated to. Two values imply a starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
*
* @param target The object whose property is to be animated. This object should
* have a public method on it called <code>setName()</code>, where <code>name</code> is
* the value of the <code>propertyName</code> parameter.
* @param propertyName The name of the property being animated.
* @param values A set of values that the animation will animate between over time.
- * @return A ValueAnimator object that is set up to animate between the given values.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
*/
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
@@ -187,21 +212,40 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
- * A constructor that takes <code>PropertyValueHolder</code> values. This constructor should
- * be used when animating several properties at once with the same ObjectAnimator, since
- * PropertyValuesHolder allows you to associate a set of animation values with a property
- * name.
+ * Constructs and returns an ObjectAnimator that animates between float values. A single
+ * value implies that that value is the one being animated to. Two values imply a starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property,
+ float... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setFloatValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to. Two values imply a starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
*
* @param target The object whose property is to be animated. This object should
- * have public methods on it called <code>setName()</code>, where <code>name</code> is
- * the name of the property passed in as the <code>propertyName</code> parameter for
- * each of the PropertyValuesHolder objects.
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
* @param propertyName The name of the property being animated.
* @param evaluator A TypeEvaluator that will be called on each animation frame to
- * provide the ncessry interpolation between the Object values to derive the animated
+ * provide the necessary interpolation between the Object values to derive the animated
* value.
- * @param values The PropertyValuesHolder objects which hold each the property name and values
- * to animate that property between.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
*/
public static ObjectAnimator ofObject(Object target, String propertyName,
TypeEvaluator evaluator, Object... values) {
@@ -212,19 +256,44 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
- * Constructs and returns an ObjectAnimator that animates between the sets of values
- * specifed in <code>PropertyValueHolder</code> objects. This variant should
- * be used when animating several properties at once with the same ObjectAnimator, since
- * PropertyValuesHolder allows you to associate a set of animation values with a property
- * name.
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to. Two values imply a starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
*
- * @param target The object whose property is to be animated. This object should
- * have public methods on it called <code>setName()</code>, where <code>name</code> is
- * the name of the property passed in as the <code>propertyName</code> parameter for
- * each of the PropertyValuesHolder objects.
- * @param values A set of PropertyValuesHolder objects whose values will be animated
- * between over time.
- * @return A ValueAnimator object that is set up to animate between the given values.
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property,
+ TypeEvaluator<V> evaluator, V... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between the sets of values specified
+ * in <code>PropertyValueHolder</code> objects. This variant should be used when animating
+ * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows
+ * you to associate a set of animation values with a property name.
+ *
+ * @param target The object whose property is to be animated. Depending on how the
+ * PropertyValuesObjects were constructed, the target object should either have the {@link
+ * android.util.Property} objects used to construct the PropertyValuesHolder objects or (if the
+ * PropertyValuesHOlder objects were created with property names) the target object should have
+ * public methods on it called <code>setName()</code>, where <code>name</code> is the name of
+ * the property passed in as the <code>propertyName</code> parameter for each of the
+ * PropertyValuesHolder objects.
+ * @param values A set of PropertyValuesHolder objects whose values will be animated between
+ * over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
*/
public static ObjectAnimator ofPropertyValuesHolder(Object target,
PropertyValuesHolder... values) {
@@ -239,7 +308,11 @@ public final class ObjectAnimator extends ValueAnimator {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
- setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofInt(mProperty, values));
+ } else {
+ setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
+ }
} else {
super.setIntValues(values);
}
@@ -250,7 +323,11 @@ public final class ObjectAnimator extends ValueAnimator {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
- setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofFloat(mProperty, values));
+ } else {
+ setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
+ }
} else {
super.setFloatValues(values);
}
@@ -261,7 +338,11 @@ public final class ObjectAnimator extends ValueAnimator {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
- setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values));
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator)null, values));
+ } else {
+ setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values));
+ }
} else {
super.setObjectValues(values);
}
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 6f91fc0..58f23f7 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -16,7 +16,10 @@
package android.animation;
+import android.util.FloatProperty;
+import android.util.IntProperty;
import android.util.Log;
+import android.util.Property;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -39,6 +42,11 @@ public class PropertyValuesHolder implements Cloneable {
String mPropertyName;
/**
+ * @hide
+ */
+ protected Property mProperty;
+
+ /**
* The setter function, if needed. ObjectAnimator hands off this functionality to
* PropertyValuesHolder, since it holds all of the per-property information. This
* property is automatically
@@ -124,6 +132,17 @@ public class PropertyValuesHolder implements Cloneable {
}
/**
+ * Internal utility constructor, used by the factory methods to set the property.
+ * @param property The property for this holder.
+ */
+ private PropertyValuesHolder(Property property) {
+ mProperty = property;
+ if (property != null) {
+ mPropertyName = property.getName();
+ }
+ }
+
+ /**
* Constructs and returns a PropertyValuesHolder with a given property name and
* set of int values.
* @param propertyName The name of the property being animated.
@@ -131,8 +150,18 @@ public class PropertyValuesHolder implements Cloneable {
* @return PropertyValuesHolder The constructed PropertyValuesHolder object.
*/
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
- PropertyValuesHolder pvh = new IntPropertyValuesHolder(propertyName, values);
- return pvh;
+ return new IntPropertyValuesHolder(propertyName, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of int values.
+ * @param property The property being animated. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
+ return new IntPropertyValuesHolder(property, values);
}
/**
@@ -143,18 +172,28 @@ public class PropertyValuesHolder implements Cloneable {
* @return PropertyValuesHolder The constructed PropertyValuesHolder object.
*/
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
- PropertyValuesHolder pvh = new FloatPropertyValuesHolder(propertyName, values);
- return pvh;
+ return new FloatPropertyValuesHolder(propertyName, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of float values.
+ * @param property The property being animated. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
+ return new FloatPropertyValuesHolder(property, values);
}
/**
* Constructs and returns a PropertyValuesHolder with a given property name and
* set of Object values. This variant also takes a TypeEvaluator because the system
- * cannot interpolate between objects of unknown type.
+ * cannot automatically interpolate between objects of unknown type.
*
* @param propertyName The name of the property being animated.
* @param evaluator A TypeEvaluator that will be called on each animation frame to
- * provide the ncessry interpolation between the Object values to derive the animated
+ * provide the necessary interpolation between the Object values to derive the animated
* value.
* @param values The values that the named property will animate between.
* @return PropertyValuesHolder The constructed PropertyValuesHolder object.
@@ -168,6 +207,26 @@ public class PropertyValuesHolder implements Cloneable {
}
/**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type.
+ *
+ * @param property The property being animated. Should not be null.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static <V> PropertyValuesHolder ofObject(Property property,
+ TypeEvaluator<V> evaluator, V... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
* Constructs and returns a PropertyValuesHolder object with the specified property name and set
* of values. These values can be of any type, but the type should be consistent so that
* an appropriate {@link android.animation.TypeEvaluator} can be found that matches
@@ -202,6 +261,37 @@ public class PropertyValuesHolder implements Cloneable {
}
/**
+ * Constructs and returns a PropertyValuesHolder object with the specified property and set
+ * of values. These values can be of any type, but the type should be consistent so that
+ * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+ * the common type.
+ * <p>If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling the property's
+ * {@link android.util.Property#get(Object)} function.
+ * Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction with
+ * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ * @param property The property associated with this set of values. Should not be null.
+ * @param values The set of values to animate between.
+ */
+ public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ if (keyframeSet instanceof IntKeyframeSet) {
+ return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet);
+ } else if (keyframeSet instanceof FloatKeyframeSet) {
+ return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet);
+ }
+ else {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.mKeyframeSet = keyframeSet;
+ pvh.mValueType = ((Keyframe)values[0]).getType();
+ return pvh;
+ }
+ }
+
+ /**
* Set the animated values for this object to this set of ints.
* If there is only one value, it is assumed to be the end value of an animation,
* and an initial value will be derived, if possible, by calling a getter function
@@ -349,7 +439,6 @@ public class PropertyValuesHolder implements Cloneable {
// Have to lock property map prior to reading it, to guard against
// another thread putting something in there after we've checked it
// but before we've added an entry to it
- // TODO: can we store the setter/getter per Class instead of per Object?
mPropertyMapLock.writeLock().lock();
HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
if (propertyMap != null) {
@@ -395,6 +484,22 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The object on which the setter (and possibly getter) exist.
*/
void setupSetterAndGetter(Object target) {
+ if (mProperty != null) {
+ // check to make sure that mProperty is on the class of target
+ try {
+ Object testValue = mProperty.get(target);
+ for (Keyframe kf : mKeyframeSet.mKeyframes) {
+ if (!kf.hasValue()) {
+ kf.setValue(mProperty.get(target));
+ }
+ }
+ return;
+ } catch (ClassCastException e) {
+ Log.e("PropertyValuesHolder","No such property (" + mProperty.getName() +
+ ") on target object " + target + ". Trying reflection instead");
+ mProperty = null;
+ }
+ }
Class targetClass = target.getClass();
if (mSetter == null) {
setupSetter(targetClass);
@@ -423,6 +528,9 @@ public class PropertyValuesHolder implements Cloneable {
* @param kf The keyframe which holds the property name and value.
*/
private void setupValue(Object target, Keyframe kf) {
+ if (mProperty != null) {
+ kf.setValue(mProperty.get(target));
+ }
try {
if (mGetter == null) {
Class targetClass = target.getClass();
@@ -465,6 +573,7 @@ public class PropertyValuesHolder implements Cloneable {
try {
PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
newPVH.mPropertyName = mPropertyName;
+ newPVH.mProperty = mProperty;
newPVH.mKeyframeSet = mKeyframeSet.clone();
newPVH.mEvaluator = mEvaluator;
return newPVH;
@@ -482,6 +591,9 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The target object on which the value is set
*/
void setAnimatedValue(Object target) {
+ if (mProperty != null) {
+ mProperty.set(target, getAnimatedValue());
+ }
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
@@ -558,6 +670,18 @@ public class PropertyValuesHolder implements Cloneable {
}
/**
+ * Sets the property that will be animated.
+ *
+ * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property
+ * must exist on the target object specified in that ObjectAnimator.</p>
+ *
+ * @param property The property being animated.
+ */
+ public void setProperty(Property property) {
+ mProperty = property;
+ }
+
+ /**
* Gets the name of the property that will be animated. This name will be used to derive
* a setter function that will be called to set animated values.
* For example, a property name of <code>foo</code> will result
@@ -597,17 +721,22 @@ public class PropertyValuesHolder implements Cloneable {
* specified above.
*/
static String getMethodName(String prefix, String propertyName) {
- char firstLetter = propertyName.charAt(0);
+ if (propertyName == null || propertyName.length() == 0) {
+ // shouldn't get here
+ return prefix;
+ }
+ char firstLetter = Character.toUpperCase(propertyName.charAt(0));
String theRest = propertyName.substring(1);
- firstLetter = Character.toUpperCase(firstLetter);
return prefix + firstLetter + theRest;
}
static class IntPropertyValuesHolder extends PropertyValuesHolder {
+ // Cache JNI functions to avoid looking them up twice
private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
new HashMap<Class, HashMap<String, Integer>>();
int mJniSetter;
+ private IntProperty mIntProperty;
IntKeyframeSet mIntKeyframeSet;
int mIntAnimatedValue;
@@ -619,11 +748,29 @@ public class PropertyValuesHolder implements Cloneable {
mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
}
+ public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) {
+ super(property);
+ mValueType = int.class;
+ mKeyframeSet = keyframeSet;
+ mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) mProperty;
+ }
+ }
+
public IntPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);
setIntValues(values);
}
+ public IntPropertyValuesHolder(Property property, int... values) {
+ super(property);
+ setIntValues(values);
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) mProperty;
+ }
+ }
+
@Override
public void setIntValues(int... values) {
super.setIntValues(values);
@@ -656,6 +803,14 @@ public class PropertyValuesHolder implements Cloneable {
*/
@Override
void setAnimatedValue(Object target) {
+ if (mIntProperty != null) {
+ mIntProperty.setValue(target, mIntAnimatedValue);
+ return;
+ }
+ if (mProperty != null) {
+ mProperty.set(target, mIntAnimatedValue);
+ return;
+ }
if (mJniSetter != 0) {
nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
return;
@@ -674,6 +829,9 @@ public class PropertyValuesHolder implements Cloneable {
@Override
void setupSetter(Class targetClass) {
+ if (mProperty != null) {
+ return;
+ }
// Check new static hashmap<propName, int> for setter method
try {
mPropertyMapLock.writeLock().lock();
@@ -696,7 +854,8 @@ public class PropertyValuesHolder implements Cloneable {
}
}
} catch (NoSuchMethodError e) {
- // System.out.println("Can't find native method using JNI, use reflection" + e);
+ Log.d("PropertyValuesHolder",
+ "Can't find native method using JNI, use reflection" + e);
} finally {
mPropertyMapLock.writeLock().unlock();
}
@@ -709,9 +868,11 @@ public class PropertyValuesHolder implements Cloneable {
static class FloatPropertyValuesHolder extends PropertyValuesHolder {
+ // Cache JNI functions to avoid looking them up twice
private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
new HashMap<Class, HashMap<String, Integer>>();
int mJniSetter;
+ private FloatProperty mFloatProperty;
FloatKeyframeSet mFloatKeyframeSet;
float mFloatAnimatedValue;
@@ -723,11 +884,29 @@ public class PropertyValuesHolder implements Cloneable {
mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
}
+ public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) {
+ super(property);
+ mValueType = float.class;
+ mKeyframeSet = keyframeSet;
+ mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) mProperty;
+ }
+ }
+
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}
+ public FloatPropertyValuesHolder(Property property, float... values) {
+ super(property);
+ setFloatValues(values);
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) mProperty;
+ }
+ }
+
@Override
public void setFloatValues(float... values) {
super.setFloatValues(values);
@@ -760,6 +939,14 @@ public class PropertyValuesHolder implements Cloneable {
*/
@Override
void setAnimatedValue(Object target) {
+ if (mFloatProperty != null) {
+ mFloatProperty.setValue(target, mFloatAnimatedValue);
+ return;
+ }
+ if (mProperty != null) {
+ mProperty.set(target, mFloatAnimatedValue);
+ return;
+ }
if (mJniSetter != 0) {
nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
return;
@@ -778,6 +965,9 @@ public class PropertyValuesHolder implements Cloneable {
@Override
void setupSetter(Class targetClass) {
+ if (mProperty != null) {
+ return;
+ }
// Check new static hashmap<propName, int> for setter method
try {
mPropertyMapLock.writeLock().lock();
@@ -800,7 +990,8 @@ public class PropertyValuesHolder implements Cloneable {
}
}
} catch (NoSuchMethodError e) {
- // System.out.println("Can't find native method using JNI, use reflection" + e);
+ Log.d("PropertyValuesHolder",
+ "Can't find native method using JNI, use reflection" + e);
} finally {
mPropertyMapLock.writeLock().unlock();
}
diff --git a/core/java/android/animation/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java
index fa49175..e738da1 100644
--- a/core/java/android/animation/TypeEvaluator.java
+++ b/core/java/android/animation/TypeEvaluator.java
@@ -24,7 +24,7 @@ package android.animation;
*
* @see ValueAnimator#setEvaluator(TypeEvaluator)
*/
-public interface TypeEvaluator {
+public interface TypeEvaluator<T> {
/**
* This function returns the result of linearly interpolating the start and end values, with
@@ -39,6 +39,6 @@ public interface TypeEvaluator {
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
- public Object evaluate(float fraction, Object startValue, Object endValue);
+ public T evaluate(float fraction, T startValue, T endValue);
} \ No newline at end of file
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f562851..1dcaa04 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1173,13 +1173,11 @@ public class ValueAnimator extends Animator {
if (oldValues != null) {
int numValues = oldValues.length;
anim.mValues = new PropertyValuesHolder[numValues];
- for (int i = 0; i < numValues; ++i) {
- anim.mValues[i] = oldValues[i].clone();
- }
anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
- PropertyValuesHolder valuesHolder = mValues[i];
- anim.mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
+ PropertyValuesHolder newValuesHolder = oldValues[i].clone();
+ anim.mValues[i] = newValuesHolder;
+ anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
}
}
return anim;
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index fc5fac6..cac06ec 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -107,6 +107,18 @@ public abstract class ActionBar {
public static final int DISPLAY_SHOW_CUSTOM = 0x10;
/**
+ * Disable the 'home' element. This may be combined with
+ * {@link #DISPLAY_SHOW_HOME} to create a non-focusable/non-clickable
+ * 'home' element. Useful for a level of your app's navigation hierarchy
+ * where clicking 'home' doesn't do anything.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ * @see #setDisplayDisableHomeEnabled(boolean)
+ */
+ public static final int DISPLAY_DISABLE_HOME = 0x20;
+
+ /**
* Set the action bar into custom navigation mode, supplying a view
* for custom navigation.
*
@@ -160,6 +172,66 @@ public abstract class ActionBar {
public abstract void setCustomView(int resId);
/**
+ * Set the icon to display in the 'home' section of the action bar.
+ * The action bar will use an icon specified by its style or the
+ * activity icon by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param resId Resource ID of a drawable to show as an icon.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setIcon(int resId);
+
+ /**
+ * Set the icon to display in the 'home' section of the action bar.
+ * The action bar will use an icon specified by its style or the
+ * activity icon by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param icon Drawable to show as an icon.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setIcon(Drawable icon);
+
+ /**
+ * Set the logo to display in the 'home' section of the action bar.
+ * The action bar will use a logo specified by its style or the
+ * activity logo by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param resId Resource ID of a drawable to show as a logo.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setLogo(int resId);
+
+ /**
+ * Set the logo to display in the 'home' section of the action bar.
+ * The action bar will use a logo specified by its style or the
+ * activity logo by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param logo Drawable to show as a logo.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setLogo(Drawable logo);
+
+ /**
* Set the adapter and navigation callback for list navigation mode.
*
* The supplied adapter will provide views for the expanded list as well as
@@ -333,6 +405,21 @@ public abstract class ActionBar {
public abstract void setDisplayShowCustomEnabled(boolean showCustom);
/**
+ * Set whether the 'home' affordance on the action bar should be disabled.
+ * If set, the 'home' element will not be focusable or clickable, useful if
+ * the user is at the top level of the app's navigation hierarchy.
+ *
+ * <p>To set several display options at once, see the setDisplayOptions methods.
+ *
+ * @param disableHome true to disable the 'home' element.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ * @see #DISPLAY_DISABLE_HOME
+ */
+ public abstract void setDisplayDisableHomeEnabled(boolean disableHome);
+
+ /**
* Set the ActionBar's background.
*
* @param d Background drawable
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b739e10..0481158 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -51,7 +51,6 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
@@ -1399,6 +1398,10 @@ public class Activity extends ContextThemeWrapper
public void onConfigurationChanged(Configuration newConfig) {
mCalled = true;
+ if (mActionBar != null) {
+ mActionBar.onConfigurationChanged(newConfig);
+ }
+
mFragments.dispatchConfigurationChanged(newConfig);
if (mWindow != null) {
@@ -2488,6 +2491,7 @@ public class Activity extends ContextThemeWrapper
break;
case Window.FEATURE_ACTION_BAR:
+ initActionBar();
mActionBar.dispatchMenuVisibilityChanged(false);
break;
}
@@ -3633,7 +3637,7 @@ public class Activity extends ContextThemeWrapper
resultCode = mResultCode;
resultData = mResultData;
}
- if (Config.LOGV) Log.v(TAG, "Finishing self: token=" + mToken);
+ if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
@@ -4571,7 +4575,7 @@ public class Activity extends ContextThemeWrapper
void dispatchActivityResult(String who, int requestCode,
int resultCode, Intent data) {
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+ ", resCode=" + resultCode + ", data=" + data);
mFragments.noteStateNotSaved();
diff --git a/core/java/android/app/ActivityGroup.java b/core/java/android/app/ActivityGroup.java
index f1216f9..5b04253 100644
--- a/core/java/android/app/ActivityGroup.java
+++ b/core/java/android/app/ActivityGroup.java
@@ -110,7 +110,7 @@ public class ActivityGroup extends Activity {
if (who != null) {
Activity act = mLocalActivityManager.getActivity(who);
/*
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+ ", resCode=" + resultCode + ", data=" + data
+ ", rec=" + rec);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 48b8ca8..a6658cc 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,6 +16,9 @@
package android.app;
+import com.android.internal.app.IUsageStats;
+import com.android.internal.os.PkgUsageStats;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,18 +29,17 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Debug;
-import android.os.RemoteException;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
-import com.android.internal.app.IUsageStats;
-import com.android.internal.os.PkgUsageStats;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -47,8 +49,7 @@ import java.util.Map;
*/
public class ActivityManager {
private static String TAG = "ActivityManager";
- private static boolean DEBUG = false;
- private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+ private static boolean localLOGV = false;
private final Context mContext;
private final Handler mHandler;
@@ -302,13 +303,6 @@ public class ActivityManager {
public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
/**
- * Flag for use with {@link #getRecentTasks}: also return the thumbnail
- * bitmap (if available) for each recent task.
- * @hide
- */
- public static final int TASKS_GET_THUMBNAILS = 0x0001000;
-
- /**
* Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
@@ -338,7 +332,7 @@ public class ActivityManager {
/**
* Information you can retrieve about a particular task that is currently
* "running" in the system. Note that a running task does not mean the
- * given task actual has a process it is actively running in; it simply
+ * given task actually has a process it is actively running in; it simply
* means that the user has gone to it and never closed it, but currently
* the system may have killed its process and is only holding on to its
* last state in order to restart it when the user returns.
@@ -493,10 +487,118 @@ public class ActivityManager {
return getRunningTasks(maxNum, 0, null);
}
+ /**
+ * Remove some end of a task's activity stack that is not part of
+ * the main application. The selected activities will be finished, so
+ * they are no longer part of the main task.
+ *
+ * @param taskId The identifier of the task.
+ * @param subTaskIndex The number of the sub-task; this corresponds
+ * to the index of the thumbnail returned by {@link #getTaskThumbnails(int)}.
+ * @return Returns true if the sub-task was found and was removed.
+ *
+ * @hide
+ */
+ public boolean removeSubTask(int taskId, int subTaskIndex)
+ throws SecurityException {
+ try {
+ return ActivityManagerNative.getDefault().removeSubTask(taskId, subTaskIndex);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return false;
+ }
+ }
+
+ /**
+ * If set, the process of the root activity of the task will be killed
+ * as part of removing the task.
+ * @hide
+ */
+ public static final int REMOVE_TASK_KILL_PROCESS = 0x0001;
+
+ /**
+ * Completely remove the given task.
+ *
+ * @param taskId Identifier of the task to be removed.
+ * @param flags Additional operational flags. May be 0 or
+ * {@link #REMOVE_TASK_KILL_PROCESS}.
+ * @return Returns true if the given task was found and removed.
+ *
+ * @hide
+ */
+ public boolean removeTask(int taskId, int flags)
+ throws SecurityException {
+ try {
+ return ActivityManagerNative.getDefault().removeTask(taskId, flags);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return false;
+ }
+ }
+
+ /** @hide */
+ public static class TaskThumbnails implements Parcelable {
+ public Bitmap mainThumbnail;
+
+ public int numSubThumbbails;
+
+ /** @hide */
+ public IThumbnailRetriever retriever;
+
+ public TaskThumbnails() {
+ }
+
+ public Bitmap getSubThumbnail(int index) {
+ try {
+ return retriever.getThumbnail(index);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mainThumbnail != null) {
+ dest.writeInt(1);
+ mainThumbnail.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(numSubThumbbails);
+ dest.writeStrongInterface(retriever);
+ }
+
+ public void readFromParcel(Parcel source) {
+ if (source.readInt() != 0) {
+ mainThumbnail = Bitmap.CREATOR.createFromParcel(source);
+ } else {
+ mainThumbnail = null;
+ }
+ numSubThumbbails = source.readInt();
+ retriever = IThumbnailRetriever.Stub.asInterface(source.readStrongBinder());
+ }
+
+ public static final Creator<TaskThumbnails> CREATOR = new Creator<TaskThumbnails>() {
+ public TaskThumbnails createFromParcel(Parcel source) {
+ return new TaskThumbnails(source);
+ }
+ public TaskThumbnails[] newArray(int size) {
+ return new TaskThumbnails[size];
+ }
+ };
+
+ private TaskThumbnails(Parcel source) {
+ readFromParcel(source);
+ }
+ }
+
/** @hide */
- public Bitmap getTaskThumbnail(int id) throws SecurityException {
+ public TaskThumbnails getTaskThumbnails(int id) throws SecurityException {
try {
- return ActivityManagerNative.getDefault().getTaskThumbnail(id);
+ return ActivityManagerNative.getDefault().getTaskThumbnails(id);
} catch (RemoteException e) {
// System dead, we will be dead too soon!
return null;
@@ -712,7 +814,7 @@ public class ActivityManager {
public List<RunningServiceInfo> getRunningServices(int maxNum)
throws SecurityException {
try {
- return (List<RunningServiceInfo>)ActivityManagerNative.getDefault()
+ return ActivityManagerNative.getDefault()
.getServices(maxNum, 0);
} catch (RemoteException e) {
// System dead, we will be dead too soon!
@@ -1367,4 +1469,17 @@ public class ActivityManager {
return new HashMap<String, Integer>();
}
}
+
+ /**
+ * @param userid the user's id. Zero indicates the default user
+ * @hide
+ */
+ public boolean switchUser(int userid) {
+ try {
+ return ActivityManagerNative.getDefault().switchUser(userid);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 88293e8..85f40c9 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -39,7 +39,6 @@ import android.os.Parcel;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import android.util.Singleton;
@@ -442,10 +441,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case GET_TASK_THUMBNAIL_TRANSACTION: {
+ case GET_TASK_THUMBNAILS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int id = data.readInt();
- Bitmap bm = getTaskThumbnail(id);
+ ActivityManager.TaskThumbnails bm = getTaskThumbnails(id);
reply.writeNoException();
if (bm != null) {
reply.writeInt(1);
@@ -1436,6 +1435,53 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
+
+ case SWITCH_USER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userid = data.readInt();
+ boolean result = switchUser(userid);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
+ case REMOVE_SUB_TASK_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ int subTaskIndex = data.readInt();
+ boolean result = removeSubTask(taskId, subTaskIndex);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
+ case REMOVE_TASK_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ int fl = data.readInt();
+ boolean result = removeTask(taskId, fl);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
+ case REGISTER_PROCESS_OBSERVER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IProcessObserver observer = IProcessObserver.Stub.asInterface(
+ data.readStrongBinder());
+ registerProcessObserver(observer);
+ return true;
+ }
+
+ case UNREGISTER_PROCESS_OBSERVER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IProcessObserver observer = IProcessObserver.Stub.asInterface(
+ data.readStrongBinder());
+ unregisterProcessObserver(observer);
+ return true;
+ }
case GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION:
{
@@ -1469,11 +1515,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
- if (Config.LOGV) {
+ if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
- if (Config.LOGV) {
+ if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
@@ -1890,16 +1936,16 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return list;
}
- public Bitmap getTaskThumbnail(int id) throws RemoteException {
+ public ActivityManager.TaskThumbnails getTaskThumbnails(int id) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(id);
- mRemote.transact(GET_TASK_THUMBNAIL_TRANSACTION, data, reply, 0);
+ mRemote.transact(GET_TASK_THUMBNAILS_TRANSACTION, data, reply, 0);
reply.readException();
- Bitmap bm = null;
+ ActivityManager.TaskThumbnails bm = null;
if (reply.readInt() != 0) {
- bm = Bitmap.CREATOR.createFromParcel(reply);
+ bm = ActivityManager.TaskThumbnails.CREATOR.createFromParcel(reply);
}
data.recycle();
reply.recycle();
@@ -3276,5 +3322,68 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
}
+ public boolean switchUser(int userid) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userid);
+ mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
+ public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(subTaskIndex);
+ mRemote.transact(REMOVE_SUB_TASK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
+ public boolean removeTask(int taskId, int flags) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(flags);
+ mRemote.transact(REMOVE_TASK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
+ public void registerProcessObserver(IProcessObserver observer) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+ mRemote.transact(REGISTER_PROCESS_OBSERVER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+ mRemote.transact(UNREGISTER_PROCESS_OBSERVER_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 6c63c2a..1ec7a96 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -45,6 +45,7 @@ import android.graphics.Canvas;
import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.ProxyProperties;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -59,7 +60,6 @@ import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -70,7 +70,7 @@ import android.view.HardwareRenderer;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -124,12 +124,12 @@ public final class ActivityThread {
public static final String TAG = "ActivityThread";
private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
private static final boolean DEBUG = false;
- static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ static final boolean localLOGV = false;
static final boolean DEBUG_MESSAGES = false;
/** @hide */
public static final boolean DEBUG_BROADCAST = false;
private static final boolean DEBUG_RESULTS = false;
- private static final boolean DEBUG_BACKUP = false;
+ private static final boolean DEBUG_BACKUP = true;
private static final boolean DEBUG_CONFIGURATION = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
@@ -345,6 +345,7 @@ public final class ActivityThread {
static final class ServiceArgsData {
IBinder token;
+ boolean taskRemoved;
int startId;
int flags;
Intent args;
@@ -408,6 +409,8 @@ public final class ActivityThread {
String pkg;
CompatibilityInfo info;
}
+
+ native private void dumpGraphicsInfo(FileDescriptor fd);
private final class ApplicationThread extends ApplicationThreadNative {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
@@ -558,10 +561,11 @@ public final class ActivityThread {
queueOrSendMessage(H.UNBIND_SERVICE, s);
}
- public final void scheduleServiceArgs(IBinder token, int startId,
+ public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
int flags ,Intent args) {
ServiceArgsData s = new ServiceArgsData();
s.token = token;
+ s.taskRemoved = taskRemoved;
s.startId = startId;
s.flags = flags;
s.args = args;
@@ -723,9 +727,14 @@ public final class ActivityThread {
Slog.w(TAG, "dumpActivity failed", e);
}
}
-
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (args != null && args.length == 1 && args[0].equals("graphics")) {
+ pw.flush();
+ dumpGraphicsInfo(fd);
+ return;
+ }
long nativeMax = Debug.getNativeHeapSize() / 1024;
long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
@@ -747,7 +756,7 @@ public final class ActivityThread {
long dalvikFree = runtime.freeMemory() / 1024;
long dalvikAllocated = dalvikMax - dalvikFree;
long viewInstanceCount = ViewDebug.getViewInstanceCount();
- long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
+ long viewRootInstanceCount = ViewDebug.getViewAncestorInstanceCount();
long appContextInstanceCount = Debug.countInstancesOfClass(ContextImpl.class);
long activityInstanceCount = Debug.countInstancesOfClass(Activity.class);
int globalAssetCount = AssetManager.getGlobalAssetCount();
@@ -862,7 +871,7 @@ public final class ActivityThread {
pw.println(" ");
pw.println(" Objects");
- printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewRoots:",
+ printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewAncestors:",
viewRootInstanceCount);
printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", appContextInstanceCount,
@@ -935,7 +944,7 @@ public final class ActivityThread {
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
- public static final int DESTROY_ACTIVITY = 109;
+ public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
@@ -1153,8 +1162,8 @@ public final class ActivityThread {
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
}
- void maybeSnapshot() {
- if (mBoundApplication != null) {
+ private void maybeSnapshot() {
+ if (mBoundApplication != null && SamplingProfilerIntegration.isEnabled()) {
// convert the *private* ActivityThread.PackageInfo to *public* known
// android.content.pm.PackageInfo
String packageName = mBoundApplication.info.mPackageName;
@@ -2003,24 +2012,27 @@ public final class ActivityThread {
BackupAgent agent = null;
String classname = data.appInfo.backupAgentName;
- if (classname == null) {
- if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) {
- Slog.e(TAG, "Attempted incremental backup but no defined agent for "
- + packageName);
- return;
+
+ if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL
+ || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL) {
+ classname = "android.app.backup.FullBackupAgent";
+ if ((data.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // system packages can supply their own full-backup agent
+ if (data.appInfo.fullBackupAgentName != null) {
+ classname = data.appInfo.fullBackupAgentName;
+ }
}
- classname = "android.app.FullBackupAgent";
}
+
try {
IBinder binder = null;
try {
+ if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);
+
java.lang.ClassLoader cl = packageInfo.getClassLoader();
- agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance();
+ agent = (BackupAgent) cl.loadClass(classname).newInstance();
// set up the agent's context
- if (DEBUG_BACKUP) Slog.v(TAG, "Initializing BackupAgent "
- + data.appInfo.backupAgentName);
-
ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
context.setOuterContext(agent);
@@ -2033,7 +2045,8 @@ public final class ActivityThread {
// If this is during restore, fail silently; otherwise go
// ahead and let the user see the crash.
Slog.e(TAG, "Agent threw during creation: " + e);
- if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE) {
+ if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE
+ && data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE_FULL) {
throw e;
}
// falling through with 'binder' still null
@@ -2047,7 +2060,7 @@ public final class ActivityThread {
}
} catch (Exception e) {
throw new RuntimeException("Unable to create BackupAgent "
- + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ + classname + ": " + e.toString(), e);
}
}
@@ -2204,7 +2217,13 @@ public final class ActivityThread {
if (data.args != null) {
data.args.setExtrasClassLoader(s.getClassLoader());
}
- int res = s.onStartCommand(data.args, data.flags, data.startId);
+ int res;
+ if (!data.taskRemoved) {
+ res = s.onStartCommand(data.args, data.flags, data.startId);
+ } else {
+ s.onTaskRemoved(data.args);
+ res = Service.START_TASK_REMOVED_COMPLETE;
+ }
QueuedWork.waitToFinish();
@@ -2734,7 +2753,7 @@ public final class ActivityThread {
r.stopped = false;
}
if (r.activity.mDecor != null) {
- if (Config.LOGV) Slog.v(
+ if (false) Slog.v(
TAG, "Handle window " + r + " visibility: " + show);
updateVisibility(r, show);
}
@@ -3492,8 +3511,7 @@ public final class ActivityThread {
}
final void handleLowMemory() {
- ArrayList<ComponentCallbacks> callbacks
- = new ArrayList<ComponentCallbacks>();
+ ArrayList<ComponentCallbacks> callbacks;
synchronized (mPackages) {
callbacks = collectComponentCallbacksLocked(true, null);
@@ -3525,6 +3543,14 @@ public final class ActivityThread {
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName);
+ // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
+ // implementation to use the pool executor. Normally, we use the
+ // serialized executor as the default. This has to happen in the
+ // main thread so the main looper is set right.
+ if (data.appInfo.targetSdkVersion <= 12) {
+ AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
/*
* Before spawning a new process, reset the time zone to be the system time zone.
* This needs to be done because the system time zone could have changed after the
@@ -3684,12 +3710,16 @@ public final class ActivityThread {
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
- List<ProviderInfo> providers = data.providers;
- if (providers != null) {
- installContentProviders(app, providers);
- // For process that contains content providers, we want to
- // ensure that the JIT is enabled "at some point".
- mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
+ // don't bring up providers in restricted mode; they may depend on the
+ // app's custom Application class
+ if (!data.restrictedBackupMode){
+ List<ProviderInfo> providers = data.providers;
+ if (providers != null) {
+ installContentProviders(app, providers);
+ // For process that contains content providers, we want to
+ // ensure that the JIT is enabled "at some point".
+ mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
+ }
}
try {
@@ -3972,7 +4002,7 @@ public final class ActivityThread {
info.applicationInfo.sourceDir);
return null;
}
- if (Config.LOGV) Slog.v(
+ if (false) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
@@ -4015,7 +4045,7 @@ public final class ActivityThread {
sThreadLocal.set(this);
mSystemThread = system;
if (!system) {
- ViewRoot.addFirstDrawHandler(new Runnable() {
+ ViewAncestor.addFirstDrawHandler(new Runnable() {
public void run() {
ensureJitEnabled();
}
@@ -4045,7 +4075,7 @@ public final class ActivityThread {
}
}
- ViewRoot.addConfigCallback(new ComponentCallbacks() {
+ ViewAncestor.addConfigCallback(new ComponentCallbacks() {
public void onConfigurationChanged(Configuration newConfig) {
synchronized (mPackages) {
// We need to apply this change to the resources
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 5926929..4cff12f 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -33,7 +33,6 @@ import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
@@ -41,6 +40,7 @@ import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -1130,6 +1130,63 @@ final class ApplicationPackageManager extends PackageManager {
return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
+ // Multi-user support
+
+ /**
+ * @hide
+ */
+ @Override
+ public UserInfo createUser(String name, int flags) {
+ try {
+ return mPM.createUser(name, flags);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public List<UserInfo> getUsers() {
+ // TODO:
+ // Dummy code, always returns just the primary user
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>();
+ UserInfo primary = new UserInfo(0, "Root!",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ users.add(primary);
+ return users;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean removeUser(int id) {
+ try {
+ return mPM.removeUser(id);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void updateUserName(int id, String name) {
+ // TODO:
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void updateUserFlags(int id, int flags) {
+ // TODO:
+ }
+
private final ContextImpl mContext;
private final IPackageManager mPM;
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 850ad2b..dc0f529 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -223,6 +223,7 @@ public abstract class ApplicationThreadNative extends Binder
{
data.enforceInterface(IApplicationThread.descriptor);
IBinder token = data.readStrongBinder();
+ boolean taskRemoved = data.readInt() != 0;
int startId = data.readInt();
int fl = data.readInt();
Intent args;
@@ -231,7 +232,7 @@ public abstract class ApplicationThreadNative extends Binder
} else {
args = null;
}
- scheduleServiceArgs(token, startId, fl, args);
+ scheduleServiceArgs(token, taskRemoved, startId, fl, args);
return true;
}
@@ -710,11 +711,12 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public final void scheduleServiceArgs(IBinder token, int startId,
+ public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
int flags, Intent args) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
+ data.writeInt(taskRemoved ? 1 : 0);
data.writeInt(startId);
data.writeInt(flags);
if (args != null) {
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java
deleted file mode 100644
index acd20bd..0000000
--- a/core/java/android/app/FullBackupAgent.java
+++ /dev/null
@@ -1,76 +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.app;
-
-import android.app.backup.BackupAgent;
-import android.app.backup.BackupDataInput;
-import android.app.backup.BackupDataOutput;
-import android.app.backup.FileBackupHelper;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * Backs up an application's entire /data/data/&lt;package&gt;/... file system. This
- * class is used by the desktop full backup mechanism and is not intended for direct
- * use by applications.
- *
- * {@hide}
- */
-
-public class FullBackupAgent extends BackupAgent {
- // !!! TODO: turn off debugging
- private static final String TAG = "FullBackupAgent";
- private static final boolean DEBUG = true;
-
- @Override
- public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) {
- LinkedList<File> dirsToScan = new LinkedList<File>();
- ArrayList<String> allFiles = new ArrayList<String>();
-
- // build the list of files in the app's /data/data tree
- dirsToScan.add(getFilesDir());
- if (DEBUG) Log.v(TAG, "Backing up dir tree @ " + getFilesDir().getAbsolutePath() + " :");
- while (dirsToScan.size() > 0) {
- File dir = dirsToScan.removeFirst();
- File[] contents = dir.listFiles();
- if (contents != null) {
- for (File f : contents) {
- if (f.isDirectory()) {
- dirsToScan.add(f);
- } else if (f.isFile()) {
- if (DEBUG) Log.v(TAG, " " + f.getAbsolutePath());
- allFiles.add(f.getAbsolutePath());
- }
- }
- }
- }
-
- // That's the file set; now back it all up
- FileBackupHelper helper = new FileBackupHelper(this,
- allFiles.toArray(new String[allFiles.size()]));
- helper.performBackup(oldState, data, newState);
- }
-
- @Override
- public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
- }
-}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b0cbbb5..e2588cf 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -133,7 +133,7 @@ public interface IActivityManager extends IInterface {
IThumbnailReceiver receiver) throws RemoteException;
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
int flags) throws RemoteException;
- public Bitmap getTaskThumbnail(int taskId) throws RemoteException;
+ public ActivityManager.TaskThumbnails getTaskThumbnails(int taskId) throws RemoteException;
public List getServices(int maxNum, int flags) throws RemoteException;
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
throws RemoteException;
@@ -350,6 +350,16 @@ public interface IActivityManager extends IInterface {
public boolean getPackageAskScreenCompat(String packageName) throws RemoteException;
public void setPackageAskScreenCompat(String packageName, boolean ask)
throws RemoteException;
+
+ // Multi-user APIs
+ public boolean switchUser(int userid) throws RemoteException;
+
+ public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException;
+
+ public boolean removeTask(int taskId, int flags) throws RemoteException;
+
+ public void registerProcessObserver(IProcessObserver observer) throws RemoteException;
+ public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException;
/*
* Private non-Binder interfaces
@@ -524,7 +534,7 @@ public interface IActivityManager extends IInterface {
int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
int KILL_PIDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80;
- int GET_TASK_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
+ int GET_TASK_THUMBNAILS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82;
int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83;
int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
@@ -572,4 +582,9 @@ public interface IActivityManager extends IInterface {
int SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+126;
int GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+127;
int SET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+128;
+ int SWITCH_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+129;
+ int REMOVE_SUB_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+130;
+ int REMOVE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+131;
+ int REGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+132;
+ int UNREGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+133;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 93a8ff3..05a68a8 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -68,6 +68,7 @@ public interface IApplicationThread extends IInterface {
static final int BACKUP_MODE_INCREMENTAL = 0;
static final int BACKUP_MODE_FULL = 1;
static final int BACKUP_MODE_RESTORE = 2;
+ static final int BACKUP_MODE_RESTORE_FULL = 3;
void scheduleCreateBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo,
int backupMode) throws RemoteException;
void scheduleDestroyBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo)
@@ -78,8 +79,8 @@ public interface IApplicationThread extends IInterface {
Intent intent, boolean rebind) throws RemoteException;
void scheduleUnbindService(IBinder token,
Intent intent) throws RemoteException;
- void scheduleServiceArgs(IBinder token, int startId, int flags, Intent args)
- throws RemoteException;
+ void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
+ int flags, Intent args) throws RemoteException;
void scheduleStopService(IBinder token) throws RemoteException;
static final int DEBUG_OFF = 0;
static final int DEBUG_ON = 1;
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index fed2bc5..8af78fa 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -51,6 +51,7 @@ oneway interface IBackupAgent {
void doBackup(in ParcelFileDescriptor oldState,
in ParcelFileDescriptor data,
in ParcelFileDescriptor newState,
+ boolean storeApk,
int token, IBackupManager callbackBinder);
/**
@@ -78,4 +79,23 @@ oneway interface IBackupAgent {
*/
void doRestore(in ParcelFileDescriptor data, int appVersionCode,
in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);
+
+ /**
+ * Restore a single "file" to the application. The file was typically obtained from
+ * a full-backup dataset. The agent reads 'size' bytes of file content
+ * from the provided file descriptor.
+ *
+ * @param data Read-only pipe delivering the file content itself.
+ *
+ * @param size Size of the file being restored.
+ * @param type Type of file system entity, e.g. FullBackup.TYPE_DIRECTORY.
+ * @param domain Name of the file's semantic domain to which the 'path' argument is a
+ * relative path. e.g. FullBackup.DATABASE_TREE_TOKEN.
+ * @param path Relative path of the file within its semantic domain.
+ * @param mode Access mode of the file system entity, e.g. 0660.
+ * @param mtime Last modification time of the file system entity.
+ */
+ void doRestoreFile(in ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder);
}
diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl
new file mode 100644
index 0000000..2094294
--- /dev/null
+++ b/core/java/android/app/IProcessObserver.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** {@hide} */
+oneway interface IProcessObserver {
+
+ void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
+ void onProcessDied(int pid, int uid);
+
+}
diff --git a/core/java/android/app/IThumbnailRetriever.aidl b/core/java/android/app/IThumbnailRetriever.aidl
new file mode 100644
index 0000000..410cc20
--- /dev/null
+++ b/core/java/android/app/IThumbnailRetriever.aidl
@@ -0,0 +1,25 @@
+/* Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.graphics.Bitmap;
+
+/**
+ * System private API for retrieving thumbnails
+ * {@hide}
+ */
+interface IThumbnailRetriever {
+ Bitmap getThumbnail(int index);
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 3d4c966..2952e6b 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -33,7 +33,6 @@ import android.os.Process;
import android.os.SystemClock;
import android.os.ServiceManager;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.Log;
import android.view.IWindowManager;
import android.view.KeyCharacterMap;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 6541c54..4913e78 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -61,8 +61,7 @@ import android.util.Log;
public class NotificationManager
{
private static String TAG = "NotificationManager";
- private static boolean DEBUG = false;
- private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+ private static boolean localLOGV = false;
private static INotificationManager sService;
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 05b9781..c179b35 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -371,6 +371,13 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
public static final int START_REDELIVER_INTENT = 3;
/**
+ * Special constant for reporting that we are done processing
+ * {@link #onTaskRemoved(Intent)}.
+ * @hide
+ */
+ public static final int START_TASK_REMOVED_COMPLETE = 1000;
+
+ /**
* This flag is set in {@link #onStartCommand} if the Intent is a
* re-delivery of a previously delivered intent, because the service
* had previously returned {@link #START_REDELIVER_INTENT} but had been
@@ -500,6 +507,19 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
}
/**
+ * This is called if the service is currently running and the user has
+ * removed a task that comes from the service's application. If you have
+ * set {@link android.content.pm.ServiceInfo#FLAG_STOP_WITH_TASK ServiceInfo.FLAG_STOP_WITH_TASK}
+ * then you will not receive this callback; instead, the service will simply
+ * be stopped.
+ *
+ * @param rootIntent The original root Intent that was used to launch
+ * the task that is being removed.
+ */
+ public void onTaskRemoved(Intent rootIntent) {
+ }
+
+ /**
* Stop the service, if it was previously started. This is the same as
* calling {@link android.content.Context#stopService} for this particular service.
*
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 13a8b78..113c610 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -38,7 +38,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -632,7 +632,7 @@ public class WallpaperManager {
public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
try {
//Log.v(TAG, "Sending new wallpaper offsets from app...");
- ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
+ ViewAncestor.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
//Log.v(TAG, "...app returning after sending offsets!");
} catch (RemoteException e) {
@@ -670,7 +670,7 @@ public class WallpaperManager {
int x, int y, int z, Bundle extras) {
try {
//Log.v(TAG, "Sending new wallpaper offsets from app...");
- ViewRoot.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
+ ViewAncestor.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
windowToken, action, x, y, z, extras, false);
//Log.v(TAG, "...app returning after sending offsets!");
} catch (RemoteException e) {
@@ -690,7 +690,7 @@ public class WallpaperManager {
*/
public void clearWallpaperOffsets(IBinder windowToken) {
try {
- ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
+ ViewAncestor.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
windowToken, -1, -1, -1, -1);
} catch (RemoteException e) {
// Ignore.
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 1c7eb98..1c37414 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -130,6 +130,14 @@ public final class DeviceAdminInfo implements Parcelable {
*/
public static final int USES_ENCRYPTED_STORAGE = 7;
+ /**
+ * A type of policy that this device admin can use: disables use of all device cameras.
+ *
+ * <p>To control this policy, the device admin must have a "disable-camera"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_DISABLE_CAMERA = 8;
+
/** @hide */
public static class PolicyInfo {
public final int ident;
@@ -174,6 +182,9 @@ public final class DeviceAdminInfo implements Parcelable {
sPoliciesDisplayOrder.add(new PolicyInfo(USES_ENCRYPTED_STORAGE, "encrypted-storage",
com.android.internal.R.string.policylab_encryptedStorage,
com.android.internal.R.string.policydesc_encryptedStorage));
+ sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_DISABLE_CAMERA, "disable-camera",
+ com.android.internal.R.string.policylab_disableCamera,
+ com.android.internal.R.string.policydesc_disableCamera));
for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
PolicyInfo pi = sPoliciesDisplayOrder.get(i);
@@ -365,7 +376,8 @@ public final class DeviceAdminInfo implements Parcelable {
* {@link #USES_POLICY_LIMIT_PASSWORD}, {@link #USES_POLICY_WATCH_LOGIN},
* {@link #USES_POLICY_RESET_PASSWORD}, {@link #USES_POLICY_FORCE_LOCK},
* {@link #USES_POLICY_WIPE_DATA},
- * {@link #USES_POLICY_EXPIRE_PASSWORD}, {@link #USES_ENCRYPTED_STORAGE}.
+ * {@link #USES_POLICY_EXPIRE_PASSWORD}, {@link #USES_ENCRYPTED_STORAGE},
+ * {@link #USES_POLICY_DISABLE_CAMERA}.
*/
public boolean usesPolicy(int policyIdent) {
return (mUsesPolicies & (1<<policyIdent)) != 0;
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 29f8caf..473aec6 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -52,8 +52,7 @@ import android.os.Bundle;
*/
public class DeviceAdminReceiver extends BroadcastReceiver {
private static String TAG = "DevicePolicy";
- private static boolean DEBUG = false;
- private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+ private static boolean localLOGV = false;
/**
* This is the primary action that a device administrator must implement to be
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index efe2633..4147b0f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1228,6 +1228,45 @@ public class DevicePolicyManager {
}
/**
+ * Called by an application that is administering the device to disable all cameras
+ * on the device. After setting this, no applications will be able to access any cameras
+ * on the device.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param disabled Whether or not the camera should be disabled.
+ */
+ public void setCameraDisabled(ComponentName admin, boolean disabled) {
+ if (mService != null) {
+ try {
+ mService.setCameraDisabled(admin, disabled);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Determine whether or not the device's cameras have been disabled either by the current
+ * admin, if specified, or all admins.
+ * @param admin The name of the admin component to check, or null to check if any admins
+ * have disabled the camera
+ */
+ public boolean getCameraDisabled(ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getCameraDisabled(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
* @hide
*/
public void setActiveAdmin(ComponentName policyReceiver, boolean refreshing) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e8caca1..9419a62 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -79,6 +79,9 @@ interface IDevicePolicyManager {
boolean getStorageEncryption(in ComponentName who);
int getStorageEncryptionStatus();
+ void setCameraDisabled(in ComponentName who, boolean disabled);
+ boolean getCameraDisabled(in ComponentName who);
+
void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing);
boolean isAdminActive(in ComponentName policyReceiver);
List<ComponentName> getActiveAdmins();
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index cb4e0e7..63f3258 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -85,7 +85,7 @@ import java.io.IOException;
*/
public abstract class BackupAgent extends ContextWrapper {
private static final String TAG = "BackupAgent";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
public BackupAgent() {
super(null);
@@ -172,11 +172,26 @@ public abstract class BackupAgent extends ContextWrapper {
* @param newState An open, read/write ParcelFileDescriptor pointing to an
* empty file. The application should record the final backup
* state here after restoring its data from the <code>data</code> stream.
+ * When a full-backup dataset is being restored, this will be <code>null</code>.
*/
public abstract void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState)
throws IOException;
+ /**
+ * @hide
+ */
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ // empty stub implementation
+ }
+
+ /**
+ * Package-private, used only for dispatching an extra step during full backup
+ */
+ void onSaveApk(BackupDataOutput data) {
+ }
// ----- Core implementation -----
@@ -196,15 +211,22 @@ public abstract class BackupAgent extends ContextWrapper {
private class BackupServiceBinder extends IBackupAgent.Stub {
private static final String TAG = "BackupServiceBinder";
+ @Override
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState,
+ boolean storeApk,
int token, IBackupManager callbackBinder) throws RemoteException {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
if (DEBUG) Log.v(TAG, "doBackup() invoked");
BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
+
+ if (storeApk) {
+ onSaveApk(output);
+ }
+
try {
BackupAgent.this.onBackup(oldState, output, newState);
} catch (IOException ex) {
@@ -223,6 +245,7 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
+ @Override
public void doRestore(ParcelFileDescriptor data, int appVersionCode,
ParcelFileDescriptor newState,
int token, IBackupManager callbackBinder) throws RemoteException {
@@ -248,5 +271,24 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
}
+
+ @Override
+ public void doRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder) throws RemoteException {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ try {
+ callbackBinder.opComplete(token);
+ } catch (RemoteException e) {
+ // we'll time out anyway, so we're safe
+ }
+ }
+ }
}
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
new file mode 100644
index 0000000..3b70e19
--- /dev/null
+++ b/core/java/android/app/backup/FullBackup.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+
+/**
+ * Global constant definitions et cetera related to the full-backup-to-fd
+ * binary format.
+ *
+ * @hide
+ */
+public class FullBackup {
+ static final String TAG = "FullBackup";
+
+ public static final String APK_TREE_TOKEN = "a";
+ public static final String OBB_TREE_TOKEN = "obb";
+ public static final String ROOT_TREE_TOKEN = "r";
+ 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 CACHE_TREE_TOKEN = "c";
+ public static final String SHARED_STORAGE_TOKEN = "shared";
+
+ public static final String APPS_PREFIX = "apps/";
+ public static final String SHARED_PREFIX = SHARED_STORAGE_TOKEN + "/";
+
+ public static final String FULL_BACKUP_INTENT_ACTION = "fullback";
+ public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
+ public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+
+ public static final int TYPE_EOF = 0;
+ public static final int TYPE_FILE = 1;
+ public static final int TYPE_DIRECTORY = 2;
+ public static final int TYPE_SYMLINK = 3;
+
+ static public native int backupToTar(String packageName, String domain,
+ String linkdomain, String rootpath, String path, BackupDataOutput output);
+
+ static public void restoreToFile(ParcelFileDescriptor data,
+ long size, int type, long mode, long mtime, File outFile,
+ boolean doChmod) throws IOException {
+ if (type == FullBackup.TYPE_DIRECTORY) {
+ // Canonically a directory has no associated content, so we don't need to read
+ // anything from the pipe in this case. Just create the directory here and
+ // drop down to the final metadata adjustment.
+ if (outFile != null) outFile.mkdirs();
+ } else {
+ FileOutputStream out = null;
+
+ // Pull the data from the pipe, copying it to the output file, until we're done
+ try {
+ if (outFile != null) {
+ File parent = outFile.getParentFile();
+ if (!parent.exists()) {
+ // in practice this will only be for the default semantic directories,
+ // and using the default mode for those is appropriate.
+ // TODO: support the edge case of apps that have adjusted the
+ // permissions on these core directories
+ parent.mkdirs();
+ }
+ out = new FileOutputStream(outFile);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to create/open file " + outFile.getPath(), e);
+ }
+
+ byte[] buffer = new byte[32 * 1024];
+ final long origSize = size;
+ FileInputStream in = new FileInputStream(data.getFileDescriptor());
+ while (size > 0) {
+ int toRead = (size > buffer.length) ? buffer.length : (int)size;
+ int got = in.read(buffer, 0, toRead);
+ if (got <= 0) {
+ Log.w(TAG, "Incomplete read: expected " + size + " but got "
+ + (origSize - size));
+ break;
+ }
+ if (out != null) {
+ try {
+ out.write(buffer, 0, got);
+ } catch (IOException e) {
+ // Problem writing to the file. Quit copying data and delete
+ // the file, but of course keep consuming the input stream.
+ Log.e(TAG, "Unable to write to file " + outFile.getPath(), e);
+ out.close();
+ out = null;
+ outFile.delete();
+ }
+ }
+ size -= got;
+ }
+ if (out != null) out.close();
+ }
+
+ // Now twiddle the state to match the backup, assuming all went well
+ if (doChmod && outFile != null) {
+ try {
+ Libcore.os.chmod(outFile.getPath(), (int)mode);
+ } catch (ErrnoException e) {
+ e.rethrowAsIOException();
+ }
+ outFile.setLastModified(mtime);
+ }
+ }
+}
diff --git a/core/java/android/app/backup/FullBackupAgent.java b/core/java/android/app/backup/FullBackupAgent.java
new file mode 100644
index 0000000..df1c363
--- /dev/null
+++ b/core/java/android/app/backup/FullBackupAgent.java
@@ -0,0 +1,225 @@
+/*
+ * 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.app.backup;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import libcore.io.Libcore;
+import libcore.io.ErrnoException;
+import libcore.io.OsConstants;
+import libcore.io.StructStat;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+/**
+ * Backs up an application's entire /data/data/&lt;package&gt;/... file system. This
+ * class is used by the desktop full backup mechanism and is not intended for direct
+ * use by applications.
+ *
+ * {@hide}
+ */
+
+public class FullBackupAgent extends BackupAgent {
+ // !!! TODO: turn off debugging
+ private static final String TAG = "FullBackupAgent";
+ private static final boolean DEBUG = true;
+
+ PackageManager mPm;
+
+ private String mMainDir;
+ private String mFilesDir;
+ private String mDatabaseDir;
+ private String mSharedPrefsDir;
+ private String mCacheDir;
+ private String mLibDir;
+
+ private File NULL_FILE;
+
+ @Override
+ public void onCreate() {
+ NULL_FILE = new File("/dev/null");
+
+ mPm = getPackageManager();
+ try {
+ ApplicationInfo appInfo = mPm.getApplicationInfo(getPackageName(), 0);
+ mMainDir = new File(appInfo.dataDir).getAbsolutePath();
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to find package " + getPackageName());
+ throw new RuntimeException(e);
+ }
+
+ mFilesDir = getFilesDir().getAbsolutePath();
+ mDatabaseDir = getDatabasePath("foo").getParentFile().getAbsolutePath();
+ mSharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getAbsolutePath();
+ mCacheDir = getCacheDir().getAbsolutePath();
+
+ ApplicationInfo app = getApplicationInfo();
+ mLibDir = (app.nativeLibraryDir != null)
+ ? new File(app.nativeLibraryDir).getAbsolutePath()
+ : null;
+ }
+
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ // Filters, the scan queue, and the set of resulting entities
+ HashSet<String> filterSet = new HashSet<String>();
+ String packageName = getPackageName();
+
+ // Okay, start with the app's root tree, but exclude all of the canonical subdirs
+ if (mLibDir != null) {
+ filterSet.add(mLibDir);
+ }
+ filterSet.add(mCacheDir);
+ filterSet.add(mDatabaseDir);
+ filterSet.add(mSharedPrefsDir);
+ filterSet.add(mFilesDir);
+ processTree(packageName, FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data);
+
+ // Now do the same for the files dir, db dir, and shared prefs dir
+ filterSet.add(mMainDir);
+ filterSet.remove(mFilesDir);
+ processTree(packageName, FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data);
+
+ filterSet.add(mFilesDir);
+ filterSet.remove(mDatabaseDir);
+ processTree(packageName, FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data);
+
+ filterSet.add(mDatabaseDir);
+ filterSet.remove(mSharedPrefsDir);
+ processTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data);
+ }
+
+ // Scan the dir tree (if it actually exists) and process each entry we find. If the
+ // 'excludes' parameter is non-null, it is consulted each time a new file system entity
+ // is visited to see whether that entity (and its subtree, if appropriate) should be
+ // omitted from the backup process.
+ protected void processTree(String packageName, String domain, String rootPath,
+ HashSet<String> excludes, BackupDataOutput data) {
+ File rootFile = new File(rootPath);
+ if (rootFile.exists()) {
+ LinkedList<File> scanQueue = new LinkedList<File>();
+ scanQueue.add(rootFile);
+
+ while (scanQueue.size() > 0) {
+ File file = scanQueue.remove(0);
+ String filePath = file.getAbsolutePath();
+
+ // prune this subtree?
+ if (excludes != null && excludes.contains(filePath)) {
+ continue;
+ }
+
+ // If it's a directory, enqueue its contents for scanning.
+ try {
+ StructStat stat = Libcore.os.lstat(filePath);
+ if (OsConstants.S_ISLNK(stat.st_mode)) {
+ if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
+ continue;
+ } else if (OsConstants.S_ISDIR(stat.st_mode)) {
+ File[] contents = file.listFiles();
+ if (contents != null) {
+ for (File entry : contents) {
+ scanQueue.add(0, entry);
+ }
+ }
+ }
+ } catch (ErrnoException e) {
+ if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
+ continue;
+ }
+
+ // Finally, back this file up before proceeding
+ FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, data);
+ }
+ }
+ }
+
+ @Override
+ void onSaveApk(BackupDataOutput data) {
+ ApplicationInfo app = getApplicationInfo();
+ if (DEBUG) Log.i(TAG, "APK flags: system=" + ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
+ + " updated=" + ((app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)
+ + " locked=" + ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) );
+ if (DEBUG) Log.i(TAG, "codepath: " + getPackageCodePath());
+
+ // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
+ final String pkgName = getPackageName();
+ final String apkDir = new File(getPackageCodePath()).getParent();
+ FullBackup.backupToTar(pkgName, FullBackup.APK_TREE_TOKEN, null,
+ apkDir, getPackageCodePath(), data);
+
+ // Save associated .obb content if it exists and we did save the apk
+ // check for .obb and save those too
+ final File obbDir = Environment.getExternalStorageAppObbDirectory(pkgName);
+ if (obbDir != null) {
+ if (DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
+ File[] obbFiles = obbDir.listFiles();
+ if (obbFiles != null) {
+ final String obbDirName = obbDir.getAbsolutePath();
+ for (File obb : obbFiles) {
+ FullBackup.backupToTar(pkgName, FullBackup.OBB_TREE_TOKEN, null,
+ obbDirName, obb.getAbsolutePath(), data);
+ }
+ }
+ }
+ }
+
+ /**
+ * Dummy -- We're never used for restore of an incremental dataset
+ */
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ }
+
+ /**
+ * Restore the described file from the given pipe.
+ */
+ @Override
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String relpath, long mode, long mtime)
+ throws IOException {
+ String basePath = null;
+ File outFile = null;
+
+ if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
+ + " domain=" + domain + " relpath=" + relpath + " mode=" + mode
+ + " mtime=" + mtime);
+
+ // Parse out the semantic domains into the correct physical location
+ if (domain.equals(FullBackup.DATA_TREE_TOKEN)) basePath = mFilesDir;
+ else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) basePath = mDatabaseDir;
+ else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) basePath = mMainDir;
+ else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) basePath = mSharedPrefsDir;
+
+ // Not a supported output location? We need to consume the data
+ // anyway, so send it to /dev/null
+ outFile = (basePath != null) ? new File(basePath, relpath) : null;
+ if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath());
+
+ // Now that we've figured out where the data goes, send it on its way
+ FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, true);
+ }
+}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index b315b3a..bac874e 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -16,7 +16,9 @@
package android.app.backup;
+import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.os.ParcelFileDescriptor;
import android.content.Intent;
/**
@@ -121,6 +123,50 @@ interface IBackupManager {
void backupNow();
/**
+ * Write a full backup of the given package to the supplied file descriptor.
+ * The fd may be a socket or other non-seekable destination. If no package names
+ * are supplied, then every application on the device will be backed up to the output.
+ *
+ * <p>This method is <i>synchronous</i> -- it does not return until the backup has
+ * completed.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @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 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
+ * installed applications' data, not just those named in the <code>packageNames</code>
+ * parameter.
+ * @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, in String[] packageNames);
+
+ /**
+ * Restore device content from the data stream passed through the given socket. The
+ * data stream must be in the format emitted by fullBackup().
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void fullRestore(in ParcelFileDescriptor fd);
+
+ /**
+ * Confirm that the requested full backup/restore operation can proceed. The system will
+ * not actually perform the operation described to fullBackup() / fullRestore() unless the
+ * UI calls back into the Backup Manager to confirm, passing the correct token. At
+ * the same time, the UI supplies a callback Binder for progress notifications during
+ * the operation.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void acknowledgeFullBackupOrRestore(int token, boolean allow,
+ IFullBackupRestoreObserver observer);
+
+ /**
* Identify the currently selected transport. Callers must hold the
* android.permission.BACKUP permission to use this method.
*/
diff --git a/core/java/android/app/backup/IFullBackupRestoreObserver.aidl b/core/java/android/app/backup/IFullBackupRestoreObserver.aidl
new file mode 100644
index 0000000..3e0b73d
--- /dev/null
+++ b/core/java/android/app/backup/IFullBackupRestoreObserver.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+/**
+ * Observer of a full backup or restore process. The observer is told "interesting"
+ * information about an ongoing full backup or restore action.
+ *
+ * {@hide}
+ */
+
+oneway interface IFullBackupRestoreObserver {
+ /**
+ * Notification: a full backup operation has begun.
+ */
+ void onStartBackup();
+
+ /**
+ * Notification: the system has begun backing up the given package.
+ *
+ * @param name The name of the application being saved. This will typically be a
+ * user-meaningful name such as "Browser" rather than a package name such as
+ * "com.android.browser", though this is not guaranteed.
+ */
+ void onBackupPackage(String name);
+
+ /**
+ * Notification: the full backup operation has ended.
+ */
+ void onEndBackup();
+
+ /**
+ * Notification: a restore-from-full-backup operation has begun.
+ */
+ void onStartRestore();
+
+ /**
+ * Notification: the system has begun restore of the given package.
+ *
+ * @param name The name of the application being saved. This will typically be a
+ * user-meaningful name such as "Browser" rather than a package name such as
+ * "com.android.browser", though this is not guaranteed.
+ */
+ void onRestorePackage(String name);
+
+ /**
+ * Notification: the restore-from-full-backup operation has ended.
+ */
+ void onEndRestore();
+
+ /**
+ * The user's window of opportunity for confirming the operation has timed out.
+ */
+ void onTimeout();
+}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 88e00b3..a8c31f9 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -699,7 +699,7 @@ public final class BluetoothAdapter {
public boolean cancelDiscovery() {
if (getState() != STATE_ON) return false;
try {
- mService.cancelDiscovery();
+ return mService.cancelDiscovery();
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index fa55520..8a9bef0 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -603,7 +603,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
*/
public boolean setAudioState(BluetoothDevice device, int state) {
if (DBG) log("setAudioState");
- if (mService != null && isEnabled()) {
+ if (mService != null && !isDisabled()) {
try {
return mService.setAudioState(device, state);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -622,7 +622,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
*/
public int getAudioState(BluetoothDevice device) {
if (DBG) log("getAudioState");
- if (mService != null && isEnabled()) {
+ if (mService != null && !isDisabled()) {
try {
return mService.getAudioState(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -705,6 +705,11 @@ public final class BluetoothHeadset implements BluetoothProfile {
return false;
}
+ private boolean isDisabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
+ return false;
+ }
+
private boolean isValidDevice(BluetoothDevice device) {
if (device == null) return false;
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 719d730..9a13c3e 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -94,10 +94,16 @@ public final class BluetoothSocket implements Closeable {
private int mPort; /* RFCOMM channel or L2CAP psm */
+ private enum SocketState {
+ INIT,
+ CONNECTED,
+ CLOSED
+ }
+
/** prevents all native calls after destroyNative() */
- private boolean mClosed;
+ private SocketState mSocketState;
- /** protects mClosed */
+ /** protects mSocketState */
private final ReentrantReadWriteLock mLock;
/** used by native code only */
@@ -145,7 +151,7 @@ public final class BluetoothSocket implements Closeable {
}
mInputStream = new BluetoothInputStream(this);
mOutputStream = new BluetoothOutputStream(this);
- mClosed = false;
+ mSocketState = SocketState.INIT;
mLock = new ReentrantReadWriteLock();
}
@@ -195,13 +201,14 @@ public final class BluetoothSocket implements Closeable {
public void connect() throws IOException {
mLock.readLock().lock();
try {
- if (mClosed) throw new IOException("socket closed");
+ if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
if (mSdp != null) {
mPort = mSdp.doSdp(); // blocks
}
connectNative(); // blocks
+ mSocketState = SocketState.CONNECTED;
} finally {
mLock.readLock().unlock();
}
@@ -216,7 +223,7 @@ public final class BluetoothSocket implements Closeable {
// abort blocking operations on the socket
mLock.readLock().lock();
try {
- if (mClosed) return;
+ if (mSocketState == SocketState.CLOSED) return;
if (mSdp != null) {
mSdp.cancel();
}
@@ -229,7 +236,7 @@ public final class BluetoothSocket implements Closeable {
// abortNative(), so this lock should immediately acquire
mLock.writeLock().lock();
try {
- mClosed = true;
+ mSocketState = SocketState.CLOSED;
destroyNative();
} finally {
mLock.writeLock().unlock();
@@ -267,13 +274,23 @@ public final class BluetoothSocket implements Closeable {
}
/**
+ * Get the connection status of this socket, ie, whether there is an active connection with
+ * remote device.
+ * @return true if connected
+ * false if not connected
+ */
+ public boolean isConnected() {
+ return (mSocketState == SocketState.CONNECTED);
+ }
+
+ /**
* Currently returns unix errno instead of throwing IOException,
* so that BluetoothAdapter can check the error code for EADDRINUSE
*/
/*package*/ int bindListen() {
mLock.readLock().lock();
try {
- if (mClosed) return EBADFD;
+ if (mSocketState == SocketState.CLOSED) return EBADFD;
return bindListenNative();
} finally {
mLock.readLock().unlock();
@@ -283,8 +300,11 @@ public final class BluetoothSocket implements Closeable {
/*package*/ BluetoothSocket accept(int timeout) throws IOException {
mLock.readLock().lock();
try {
- if (mClosed) throw new IOException("socket closed");
- return acceptNative(timeout);
+ if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
+
+ BluetoothSocket acceptedSocket = acceptNative(timeout);
+ mSocketState = SocketState.CONNECTED;
+ return acceptedSocket;
} finally {
mLock.readLock().unlock();
}
@@ -293,7 +313,7 @@ public final class BluetoothSocket implements Closeable {
/*package*/ int available() throws IOException {
mLock.readLock().lock();
try {
- if (mClosed) throw new IOException("socket closed");
+ if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
return availableNative();
} finally {
mLock.readLock().unlock();
@@ -303,7 +323,7 @@ public final class BluetoothSocket implements Closeable {
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
mLock.readLock().lock();
try {
- if (mClosed) throw new IOException("socket closed");
+ if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
return readNative(b, offset, length);
} finally {
mLock.readLock().unlock();
@@ -313,7 +333,7 @@ public final class BluetoothSocket implements Closeable {
/*package*/ int write(byte[] b, int offset, int length) throws IOException {
mLock.readLock().lock();
try {
- if (mClosed) throw new IOException("socket closed");
+ if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
return writeNative(b, offset, length);
} finally {
mLock.readLock().unlock();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2d03e7c..364821e 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -35,7 +35,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.text.TextUtils;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
@@ -1627,9 +1626,9 @@ public abstract class ContentResolver {
return sContentService;
}
IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
- if (Config.LOGV) Log.v("ContentService", "default service binder = " + b);
+ if (false) Log.v("ContentService", "default service binder = " + b);
sContentService = IContentService.Stub.asInterface(b);
- if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService);
+ if (false) Log.v("ContentService", "default service = " + sContentService);
return sContentService;
}
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index afe8483..a2af558 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -25,7 +25,6 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.util.Config;
import android.util.Log;
import android.Manifest;
@@ -104,7 +103,7 @@ public final class ContentService extends IContentService.Stub {
}
synchronized (mRootNode) {
mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode);
- if (Config.LOGV) Log.v(TAG, "Registered observer " + observer + " at " + uri +
+ if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
" with notifyForDescendents " + notifyForDescendents);
}
}
@@ -115,7 +114,7 @@ public final class ContentService extends IContentService.Stub {
}
synchronized (mRootNode) {
mRootNode.removeObserverLocked(observer);
- if (Config.LOGV) Log.v(TAG, "Unregistered observer " + observer);
+ if (false) Log.v(TAG, "Unregistered observer " + observer);
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4c7d87f..aecec66 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1544,6 +1544,11 @@ public abstract class Context {
*/
public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
+ /** {@hide} */
+ public static final String NETWORK_STATS_SERVICE = "netstats";
+ /** {@hide} */
+ public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.net.wifi.WifiManager} for handling management of
diff --git a/core/java/android/content/IOnPrimaryClipChangedListener.aidl b/core/java/android/content/IOnPrimaryClipChangedListener.aidl
index fb42a45..46d7f7c 100644
--- a/core/java/android/content/IOnPrimaryClipChangedListener.aidl
+++ b/core/java/android/content/IOnPrimaryClipChangedListener.aidl
@@ -16,6 +16,9 @@
package android.content;
+/**
+ * {@hide}
+ */
oneway interface IOnPrimaryClipChangedListener {
void dispatchPrimaryClipChanged();
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7bdd1b9..2f9627a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1160,6 +1160,15 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
/**
+ * Activity Action: Show settings for managing network data usage of a
+ * specific application. Applications should define an activity that offers
+ * options to control data usage.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_NETWORK_USAGE =
+ "android.intent.action.MANAGE_NETWORK_USAGE";
+
+ /**
* A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
* describing the last run version of the platform that was setup.
* @hide
@@ -1654,8 +1663,9 @@ public class Intent implements Parcelable, Cloneable {
* This is used mainly for the USB Settings panel.
* Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified
* when the SD card file system is mounted or unmounted
+ * @deprecated replaced by android.os.storage.StorageEventListener
*/
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
public static final String ACTION_UMS_CONNECTED = "android.intent.action.UMS_CONNECTED";
/**
@@ -1663,8 +1673,9 @@ public class Intent implements Parcelable, Cloneable {
* This is used mainly for the USB Settings panel.
* Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified
* when the SD card file system is mounted or unmounted
+ * @deprecated replaced by android.os.storage.StorageEventListener
*/
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
public static final String ACTION_UMS_DISCONNECTED = "android.intent.action.UMS_DISCONNECTED";
/**
@@ -1878,7 +1889,7 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.USB_ANLG_HEADSET_PLUG";
/**
- * Broadcast Action: An analog audio speaker/headset plugged in or unplugged.
+ * Broadcast Action: A digital audio speaker/headset plugged in or unplugged.
*
* <p>The intent will have the following extra values:
* <ul>
@@ -1908,6 +1919,21 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.HDMI_AUDIO_PLUG";
/**
+ * <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p>
+ * <ul>
+ * <li><em>state</em> - A boolean value indicating whether the settings is on or off.</li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @hide
+ */
+ //@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ADVANCED_SETTINGS_CHANGED
+ = "android.intent.action.ADVANCED_SETTINGS";
+
+ /**
* Broadcast Action: An outgoing call is about to be placed.
*
* <p>The Intent will have the following extra value:
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 5ba5fe1..f3b1d94 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -21,7 +21,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
import android.util.AndroidException;
-import android.util.Config;
import android.util.Log;
import android.util.Printer;
@@ -669,7 +668,7 @@ public class IntentFilter implements Parcelable {
if (host == null) {
return NO_MATCH_DATA;
}
- if (Config.LOGV) Log.v("IntentFilter",
+ if (false) Log.v("IntentFilter",
"Match host " + host + ": " + mHost);
if (mWild) {
if (host.length() < mHost.length()) {
@@ -1094,14 +1093,14 @@ public class IntentFilter implements Parcelable {
public final int match(String action, String type, String scheme,
Uri data, Set<String> categories, String logTag) {
if (action != null && !matchAction(action)) {
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
logTag, "No matching action " + action + " for " + this);
return NO_MATCH_ACTION;
}
int dataMatch = matchData(type, scheme, data);
if (dataMatch < 0) {
- if (Config.LOGV) {
+ if (false) {
if (dataMatch == NO_MATCH_TYPE) {
Log.v(logTag, "No matching type " + type
+ " for " + this);
@@ -1116,7 +1115,7 @@ public class IntentFilter implements Parcelable {
String categoryMismatch = matchCategories(categories);
if (categoryMismatch != null) {
- if (Config.LOGV) {
+ if (false) {
Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
}
return NO_MATCH_CATEGORY;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2bd632d..c0a1d8e 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -89,7 +89,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* <p>If android:allowBackup is set to false, this attribute is ignored.
*/
public String backupAgentName;
-
+
+ /**
+ * Class implementing the package's *full* backup functionality. This
+ * is not usable except by system-installed packages. It can be the same
+ * as the backupAgent.
+ *
+ * @hide
+ */
+ public String fullBackupAgentName;
+
/**
* Value for {@link #flags}: if set, this application is installed in the
* device's system image.
@@ -538,6 +547,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(installLocation);
dest.writeString(manageSpaceActivityName);
dest.writeString(backupAgentName);
+ dest.writeString(fullBackupAgentName);
dest.writeInt(descriptionRes);
}
@@ -574,6 +584,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
installLocation = source.readInt();
manageSpaceActivityName = source.readString();
backupAgentName = source.readString();
+ fullBackupAgentName = source.readString();
descriptionRes = source.readInt();
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 20b1b50..37b6822 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -36,6 +36,7 @@ import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.content.IntentSender;
@@ -342,4 +343,7 @@ interface IPackageManager {
boolean setInstallLocation(int loc);
int getInstallLocation();
+
+ UserInfo createUser(in String name, int flags);
+ boolean removeUser(int userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index bb4a5ce..33c2937 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -150,21 +150,21 @@ public abstract class PackageManager {
* {@link PackageInfo#permissions}.
*/
public static final int GET_PERMISSIONS = 0x00001000;
-
+
/**
* Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
- * This state could have resulted if applications have been deleted with flag
+ * This state could have resulted if applications have been deleted with flag
* DONT_DELETE_DATA
* with a possibility of being replaced or reinstalled in future
*/
public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
-
+
/**
* {@link PackageInfo} flag: return information about
* hardware preferences in
* {@link PackageInfo#configPreferences PackageInfo.configPreferences} and
* requested features in {@link PackageInfo#reqFeatures
- * PackageInfo.reqFeatures}.
+ * PackageInfo.reqFeatures}.
*/
public static final int GET_CONFIGURATIONS = 0x00004000;
@@ -244,7 +244,7 @@ public abstract class PackageManager {
public static final int INSTALL_REPLACE_EXISTING = 0x00000002;
/**
- * Flag parameter for {@link #installPackage} to indicate that you want to
+ * Flag parameter for {@link #installPackage} to indicate that you want to
* allow test packages (those that have set android:testOnly in their
* manifest) to be installed.
* @hide
@@ -555,7 +555,7 @@ public abstract class PackageManager {
* 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.
- *
+ *
* @hide
*/
public static final int DELETE_SUCCEEDED = 1;
@@ -564,7 +564,7 @@ public abstract class PackageManager {
* Deletion failed return code: this is passed to the
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
* failed to delete the package for an unspecified reason.
- *
+ *
* @hide
*/
public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
@@ -574,7 +574,7 @@ public abstract class PackageManager {
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
* failed to delete the package because it is the active DevicePolicy
* manager.
- *
+ *
* @hide
*/
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
@@ -583,7 +583,7 @@ public abstract class PackageManager {
* 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.
- *
+ *
* @hide
*/
public static final int MOVE_SUCCEEDED = 1;
@@ -641,7 +641,7 @@ public abstract class PackageManager {
* {@link #movePackage(android.net.Uri, IPackageMoveObserver)} if the
* specified package already has an operation pending in the
* {@link PackageHandler} queue.
- *
+ *
* @hide
*/
public static final int MOVE_FAILED_OPERATION_PENDING = -7;
@@ -662,10 +662,15 @@ public abstract class PackageManager {
public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
/**
- * Feature for {@link #getSystemAvailableFeatures} and
- * {@link #hasSystemFeature}: The device's audio pipeline is low-latency,
- * more suitable for audio applications sensitive to delays or lag in
- * sound input or output.
+ * Range of IDs allocated for a user.
+ * @hide
+ */
+ public static final int PER_USER_RANGE = 100000;
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
+ * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
+ * lag in sound input or output.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
@@ -789,7 +794,7 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a telephony radio with data
@@ -797,14 +802,14 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a CDMA telephony stack.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a GSM telephony stack.
@@ -847,8 +852,8 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
-
-
+
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's touch screen supports
@@ -856,7 +861,7 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's touch screen is capable of
@@ -964,11 +969,11 @@ public abstract class PackageManager {
* @return Returns a PackageInfo object containing information about the package.
* If flag GET_UNINSTALLED_PACKAGES is set and if the package is not
* found in the list of installed applications, the package information is
- * retrieved from the list of uninstalled applications(which includes
+ * retrieved from the list of uninstalled applications(which includes
* installed applications as well as applications
* with data directory ie applications which had been
* deleted with DONT_DELTE_DATA flag set).
- *
+ *
* @see #GET_ACTIVITIES
* @see #GET_GIDS
* @see #GET_CONFIGURATIONS
@@ -979,7 +984,7 @@ public abstract class PackageManager {
* @see #GET_SERVICES
* @see #GET_SIGNATURES
* @see #GET_UNINSTALLED_PACKAGES
- *
+ *
*/
public abstract PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException;
@@ -992,7 +997,7 @@ public abstract class PackageManager {
* the canonical name for each package.
*/
public abstract String[] currentToCanonicalPackageNames(String[] names);
-
+
/**
* Map from a packages canonical name to the current name in use on the device.
* @param names Array of new names to be mapped.
@@ -1000,7 +1005,7 @@ public abstract class PackageManager {
* the current name for each package.
*/
public abstract String[] canonicalToCurrentPackageNames(String[] names);
-
+
/**
* Return a "good" intent to launch a front-door activity in a package,
* for use for example to implement an "open" button when browsing through
@@ -1008,12 +1013,12 @@ public abstract class PackageManager {
* activity in the category {@link Intent#CATEGORY_INFO}, next for a
* main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
* null if neither are found.
- *
+ *
* <p>Throws {@link NameNotFoundException} if a package with the given
* name can not be found on the system.
*
* @param packageName The name of the package to inspect.
- *
+ *
* @return Returns either a fully-qualified Intent that can be used to
* launch the main activity in the package, or null if the package does
* not contain such an activity.
@@ -1109,16 +1114,16 @@ public abstract class PackageManager {
*
* @param packageName The full name (i.e. com.google.apps.contacts) of an
* application.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
*
- * @return {@link ApplicationInfo} Returns ApplicationInfo object containing
+ * @return {@link ApplicationInfo} Returns ApplicationInfo object containing
* information about the package.
* If flag GET_UNINSTALLED_PACKAGES is set and if the package is not
- * found in the list of installed applications,
- * the application information is retrieved from the
- * list of uninstalled applications(which includes
+ * found in the list of installed applications,
+ * the application information is retrieved from the
+ * list of uninstalled applications(which includes
* installed applications as well as applications
* with data directory ie applications which had been
* deleted with DONT_DELTE_DATA flag set).
@@ -1140,7 +1145,7 @@ public abstract class PackageManager {
* @param component The full component name (i.e.
* com.google.apps.contacts/com.google.apps.contacts.ContactsList) of an Activity
* class.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data (in ApplicationInfo) returned.
*
@@ -1163,7 +1168,7 @@ public abstract class PackageManager {
* @param component The full component name (i.e.
* com.google.apps.calendar/com.google.apps.calendar.CalendarAlarm) of a Receiver
* class.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data returned.
*
@@ -1186,12 +1191,12 @@ public abstract class PackageManager {
* @param component The full component name (i.e.
* com.google.apps.media/com.google.apps.media.BackgroundPlayback) of a Service
* class.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data returned.
*
* @return ServiceInfo containing information about the service.
- *
+ *
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
*/
@@ -1238,7 +1243,7 @@ public abstract class PackageManager {
*
* @return A List of PackageInfo objects, one for each package that is
* installed on the device. In the unlikely case of there being no
- * installed packages, an empty list is returned.
+ * installed packages, an empty list is returned.
* If flag GET_UNINSTALLED_PACKAGES is set, a list of all
* applications including those deleted with DONT_DELETE_DATA
* (partially installed apps with data directory) will be returned.
@@ -1253,7 +1258,7 @@ public abstract class PackageManager {
* @see #GET_SERVICES
* @see #GET_SIGNATURES
* @see #GET_UNINSTALLED_PACKAGES
- *
+ *
*/
public abstract List<PackageInfo> getInstalledPackages(int flags);
@@ -1315,7 +1320,7 @@ public abstract class PackageManager {
* the device is rebooted before it is written.
*/
public abstract boolean addPermissionAsync(PermissionInfo info);
-
+
/**
* Removes a permission that was previously added with
* {@link #addPermission(PermissionInfo)}. The same ownership rules apply
@@ -1402,7 +1407,7 @@ public abstract class PackageManager {
* user id is not currently assigned.
*/
public abstract String getNameForUid(int uid);
-
+
/**
* Return the user id associated with a shared user name. Multiple
* applications can specify a shared user name in their manifest and thus
@@ -1423,38 +1428,38 @@ public abstract class PackageManager {
* device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
* applications including those deleted with DONT_DELETE_DATA(partially
* installed apps with data directory) will be returned.
- *
- * @param flags Additional option flags. Use any combination of
+ *
+ * @param flags Additional option flags. Use any combination of
* {@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
* is installed on the device. In the unlikely case of there being
- * no installed applications, an empty list is returned.
+ * no installed applications, an empty list is returned.
* If flag GET_UNINSTALLED_PACKAGES is set, a list of all
* applications including those deleted with DONT_DELETE_DATA
* (partially installed apps with data directory) will be returned.
- *
+ *
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
* @see #GET_UNINSTALLED_PACKAGES
*/
public abstract List<ApplicationInfo> getInstalledApplications(int flags);
-
+
/**
* Get a list of shared libraries that are available on the
* system.
- *
+ *
* @return An array of shared library names that are
* available on the system, or null if none are installed.
- *
+ *
*/
public abstract String[] getSystemSharedLibraryNames();
/**
* Get a list of features that are available on the
* system.
- *
+ *
* @return An array of FeatureInfo classes describing the features
* that are available on the system, or null if there are none(!!).
*/
@@ -1463,7 +1468,7 @@ public abstract class PackageManager {
/**
* Check whether the given feature name is one of the available
* features as returned by {@link #getSystemAvailableFeatures()}.
- *
+ *
* @return Returns true if the devices supports the feature, else
* false.
*/
@@ -1480,7 +1485,7 @@ public abstract class PackageManager {
* that {@link android.content.Context#startActivity(Intent)} and
* {@link android.content.Intent#resolveActivity(PackageManager)
* Intent.resolveActivity(PackageManager)} do.</p>
- *
+ *
* @param intent An intent containing all of the desired specification
* (action, data, type, category, and/or component).
* @param flags Additional option flags. The most important is
@@ -1779,7 +1784,7 @@ public abstract class PackageManager {
*
* @return Returns the image of the logo or null if the activity has no
* logo specified.
- *
+ *
* @throws NameNotFoundException Thrown if the resources for the given
* activity could not be loaded.
*
@@ -1800,7 +1805,7 @@ public abstract class PackageManager {
*
* @return Returns the image of the logo, or null if the activity has no
* logo specified.
- *
+ *
* @throws NameNotFoundException Thrown if the resources for application
* matching the given intent could not be loaded.
*
@@ -1833,7 +1838,7 @@ public abstract class PackageManager {
*
* @return Returns the image of the logo, or null if no application logo
* has been specified.
- *
+ *
* @throws NameNotFoundException Thrown if the resources for the given
* application could not be loaded.
*
@@ -1967,7 +1972,7 @@ public abstract class PackageManager {
* @see #GET_RECEIVERS
* @see #GET_SERVICES
* @see #GET_SIGNATURES
- *
+ *
*/
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
PackageParser packageParser = new PackageParser(archiveFilePath);
@@ -1984,7 +1989,7 @@ public abstract class PackageManager {
/**
* @hide
- *
+ *
* Install a package. Since this may take a little while, the result will
* be posted back to the given observer. An installation will fail if the calling context
* lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
@@ -2044,11 +2049,11 @@ public abstract class PackageManager {
/**
* Retrieve the package name of the application that installed a package. This identifies
* which market the package came from.
- *
+ *
* @param packageName The name of the package to query
*/
public abstract String getInstallerPackageName(String packageName);
-
+
/**
* Attempts to clear the user data directory of an application.
* Since this may take a little while, the result will
@@ -2103,7 +2108,7 @@ public abstract class PackageManager {
* of bytes if possible.
* @param observer call back used to notify when
* the operation is completed
- *
+ *
* @hide
*/
public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
@@ -2128,7 +2133,7 @@ public abstract class PackageManager {
* @param pi IntentSender call back used to
* notify when the operation is completed.May be null
* to indicate that no call back is desired.
- *
+ *
* @hide
*/
public abstract void freeStorage(long freeStorageSize, IntentSender pi);
@@ -2204,7 +2209,7 @@ public abstract class PackageManager {
* @deprecated This is a protected API that should not have been available
* to third party applications. It is the platform's responsibility for
* assigning preferred activities and this can not be directly modified.
- *
+ *
* Add a new preferred activity mapping to the system. This will be used
* to automatically select the given activity component when
* {@link Context#startActivity(Intent) Context.startActivity()} finds
@@ -2227,7 +2232,7 @@ public abstract class PackageManager {
* @deprecated This is a protected API that should not have been available
* to third party applications. It is the platform's responsibility for
* assigning preferred activities and this can not be directly modified.
- *
+ *
* Replaces an existing preferred activity mapping to the system, and if that were not present
* adds a new preferred activity. This will be used
* to automatically select the given activity component when
@@ -2336,7 +2341,7 @@ public abstract class PackageManager {
*/
public abstract void setApplicationEnabledSetting(String packageName,
int newState, int flags);
-
+
/**
* Return the the enabled setting for an application. This returns
* the last value set by
@@ -2377,4 +2382,79 @@ public abstract class PackageManager {
*/
public abstract void movePackage(
String packageName, IPackageMoveObserver observer, int flags);
+
+ /**
+ * Creates a user with the specified name and options.
+ *
+ * @param name the user's name
+ * @param flags flags that identify the type of user and other properties.
+ * @see UserInfo
+ *
+ * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @hide
+ */
+ public abstract UserInfo createUser(String name, int flags);
+
+ /**
+ * @return the list of users that were created
+ * @hide
+ */
+ public abstract List<UserInfo> getUsers();
+
+ /**
+ * @param id the ID of the user, where 0 is the primary user.
+ * @hide
+ */
+ public abstract boolean removeUser(int id);
+
+ /**
+ * Updates the user's name.
+ *
+ * @param id the user's id
+ * @param name the new name for the user
+ * @hide
+ */
+ public abstract void updateUserName(int id, String name);
+
+ /**
+ * Changes the user's properties specified by the flags.
+ *
+ * @param id the user's id
+ * @param flags the new flags for the user
+ * @hide
+ */
+ public abstract void updateUserFlags(int id, int flags);
+
+ /**
+ * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
+ * user.
+ * @hide
+ */
+ public static boolean isSameUser(int uid1, int uid2) {
+ return getUserId(uid1) == getUserId(uid2);
+ }
+
+ /**
+ * Returns the user id for a given uid.
+ * @hide
+ */
+ public static int getUserId(int uid) {
+ return uid / PER_USER_RANGE;
+ }
+
+ /**
+ * Returns the uid that is composed from the userId and the appId.
+ * @hide
+ */
+ public static int getUid(int userId, int appId) {
+ return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
+ }
+
+ /**
+ * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
+ * @hide
+ */
+ public static int getAppId(int uid) {
+ return uid % PER_USER_RANGE;
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 98ce8aa..31ad6e9 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,11 +24,11 @@ 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;
import android.util.AttributeSet;
-import android.util.Config;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
@@ -384,7 +384,7 @@ public class PackageParser {
return null;
}
- if ((flags&PARSE_CHATTY) != 0 && Config.LOGD) Log.d(
+ if ((flags&PARSE_CHATTY) != 0 && false) Log.d(
TAG, "Scanning package: " + mArchiveSourcePath);
XmlResourceParser parser = null;
@@ -666,7 +666,7 @@ public class PackageParser {
outError[0] = "No start tag found";
return null;
}
- if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+ if ((flags&PARSE_CHATTY) != 0 && false) Log.v(
TAG, "Root element name: '" + parser.getName() + "'");
if (!parser.getName().equals("manifest")) {
outError[0] = "No <manifest> tag";
@@ -701,7 +701,7 @@ public class PackageParser {
outError[0] = "No start tag found";
return null;
}
- if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+ if ((flags&PARSE_CHATTY) != 0 && false) Log.v(
TAG, "Root element name: '" + parser.getName() + "'");
if (!parser.getName().equals("manifest")) {
outError[0] = "No <manifest> tag";
@@ -1516,7 +1516,18 @@ public class PackageParser {
}
}
}
-
+
+ // fullBackupAgent is explicitly handled even if allowBackup is false
+ name = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_fullBackupAgent, 0);
+ if (name != null) {
+ ai.fullBackupAgentName = buildClassName(pkgName, name, outError);
+ if (false) {
+ Log.v(TAG, "android:fullBackupAgent=" + ai.fullBackupAgentName
+ + " from " + pkgName + "+" + name);
+ }
+ }
+
TypedValue v = sa.peekValue(
com.android.internal.R.styleable.AndroidManifestApplication_label);
if (v != null && (ai.labelRes=v.resourceId) == 0) {
@@ -2480,6 +2491,13 @@ public class PackageParser {
s.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
+ s.info.flags = 0;
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_stopWithTask,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_STOP_WITH_TASK;
+ }
+
sa.recycle();
if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 087a4fe..612e345 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -33,17 +33,35 @@ public class ServiceInfo extends ComponentInfo
*/
public String permission;
+ /**
+ * Bit in {@link #flags}: If set, the service will automatically be
+ * stopped by the system if the user removes a task that is rooted
+ * in one of the application's activities. Set from the
+ * {@link android.R.attr#stopWithTask} attribute.
+ */
+ public static final int FLAG_STOP_WITH_TASK = 0x0001;
+
+ /**
+ * Options that have been set in the service declaration in the
+ * manifest.
+ * These include:
+ * {@link #FLAG_STOP_WITH_TASK}
+ */
+ public int flags;
+
public ServiceInfo() {
}
public ServiceInfo(ServiceInfo orig) {
super(orig);
permission = orig.permission;
+ flags = orig.flags;
}
public void dump(Printer pw, String prefix) {
super.dumpFront(pw, prefix);
pw.println(prefix + "permission=" + permission);
+ pw.println(prefix + "flags=0x" + Integer.toHexString(flags));
}
public String toString() {
@@ -59,6 +77,7 @@ public class ServiceInfo extends ComponentInfo
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
dest.writeString(permission);
+ dest.writeInt(flags);
}
public static final Creator<ServiceInfo> CREATOR =
@@ -74,5 +93,6 @@ public class ServiceInfo extends ComponentInfo
private ServiceInfo(Parcel source) {
super(source);
permission = source.readString();
+ flags = source.readInt();
}
}
diff --git a/core/java/android/content/pm/UserInfo.aidl b/core/java/android/content/pm/UserInfo.aidl
new file mode 100644
index 0000000..2e7cb8f
--- /dev/null
+++ b/core/java/android/content/pm/UserInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm;
+
+parcelable UserInfo;
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
new file mode 100644
index 0000000..ba5331c
--- /dev/null
+++ b/core/java/android/content/pm/UserInfo.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Per-user information.
+ * @hide
+ */
+public class UserInfo implements Parcelable {
+ /**
+ * Primary user. Only one user can have this flag set. Meaning of this
+ * flag TBD.
+ */
+ public static final int FLAG_PRIMARY = 0x00000001;
+
+ /**
+ * User with administrative privileges. Such a user can create and
+ * delete users.
+ */
+ public static final int FLAG_ADMIN = 0x00000002;
+
+ /**
+ * Indicates a guest user that may be transient.
+ */
+ public static final int FLAG_GUEST = 0x00000004;
+
+ public int id;
+ public String name;
+ public int flags;
+
+ public UserInfo(int id, String name, int flags) {
+ this.id = id;
+ this.name = name;
+ this.flags = flags;
+ }
+
+ public boolean isPrimary() {
+ return (flags & FLAG_PRIMARY) == FLAG_PRIMARY;
+ }
+
+ public boolean isAdmin() {
+ return (flags & FLAG_ADMIN) == FLAG_ADMIN;
+ }
+
+ public boolean isGuest() {
+ return (flags & FLAG_GUEST) == FLAG_GUEST;
+ }
+
+ public UserInfo() {
+ }
+
+ public UserInfo(UserInfo orig) {
+ name = orig.name;
+ id = orig.id;
+ flags = orig.flags;
+ }
+
+ @Override
+ public String toString() {
+ return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(id);
+ dest.writeString(name);
+ dest.writeInt(flags);
+ }
+
+ public static final Parcelable.Creator<UserInfo> CREATOR
+ = new Parcelable.Creator<UserInfo>() {
+ public UserInfo createFromParcel(Parcel source) {
+ return new UserInfo(source);
+ }
+ public UserInfo[] newArray(int size) {
+ return new UserInfo[size];
+ }
+ };
+
+ private UserInfo(Parcel source) {
+ id = source.readInt();
+ name = source.readString();
+ flags = source.readInt();
+ }
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index dbb4271..931cb18 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -17,7 +17,6 @@
package android.content.res;
import android.os.ParcelFileDescriptor;
-import android.util.Config;
import android.util.Log;
import android.util.TypedValue;
@@ -58,7 +57,7 @@ public final class AssetManager {
public static final int ACCESS_BUFFER = 3;
private static final String TAG = "AssetManager";
- private static final boolean localLOGV = Config.LOGV || false;
+ private static final boolean localLOGV = false || false;
private static final boolean DEBUG_REFS = false;
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index e2c6483..906a564 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -277,6 +277,26 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public int compatSmallestScreenWidthDp;
/**
+ * @hide
+ */
+ public static final int LAYOUT_DIRECTION_UNDEFINED = -1;
+
+ /**
+ * @hide
+ */
+ public static final int LAYOUT_DIRECTION_LTR = 0;
+
+ /**
+ * @hide
+ */
+ public static final int LAYOUT_DIRECTION_RTL = 1;
+
+ /**
+ * @hide The layout direction associated to the current Locale
+ */
+ public int layoutDirection;
+
+ /**
* @hide Internal book-keeping.
*/
public int seq;
@@ -302,6 +322,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
mnc = o.mnc;
if (o.locale != null) {
locale = (Locale) o.locale.clone();
+ layoutDirection = o.layoutDirection;
}
userSetLocale = o.userSetLocale;
touchscreen = o.touchscreen;
@@ -429,6 +450,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
case NAVIGATIONHIDDEN_YES: sb.append("/h"); break;
default: sb.append("/"); sb.append(navigationHidden); break;
}
+ switch (layoutDirection) {
+ case LAYOUT_DIRECTION_UNDEFINED: sb.append(" ?layoutdir"); break;
+ case LAYOUT_DIRECTION_LTR: sb.append(" ltr"); break;
+ case LAYOUT_DIRECTION_RTL: sb.append(" rtl"); break;
+ }
if (seq != 0) {
sb.append(" s.");
sb.append(seq);
@@ -458,6 +484,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
seq = 0;
+ layoutDirection = LAYOUT_DIRECTION_LTR;
}
/** {@hide} */
@@ -492,6 +519,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_LOCALE;
locale = delta.locale != null
? (Locale) delta.locale.clone() : null;
+ layoutDirection = getLayoutDirectionFromLocale(locale);
}
if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
{
@@ -581,6 +609,29 @@ public final class Configuration implements Parcelable, Comparable<Configuration
}
/**
+ * Return the layout direction for a given Locale
+ * @param locale the Locale for which we want the layout direction. Can be null.
+ * @return the layout direction. This may be one of {@link #LAYOUT_DIRECTION_UNDEFINED},
+ * {@link #LAYOUT_DIRECTION_LTR} or {@link #LAYOUT_DIRECTION_RTL}.
+ *
+ * @hide
+ */
+ public static int getLayoutDirectionFromLocale(Locale locale) {
+ if (locale == null || locale.equals(Locale.ROOT)) return LAYOUT_DIRECTION_UNDEFINED;
+ // Be careful: this code will need to be changed when vertical scripts will be supported
+ // OR if ICU4C is updated to have the "likelySubtags" file
+ switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ return LAYOUT_DIRECTION_LTR;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ return LAYOUT_DIRECTION_RTL;
+ default:
+ return LAYOUT_DIRECTION_UNDEFINED;
+ }
+ }
+
+ /**
* Return a bit mask of the differences between this Configuration
* object and the given one. Does not change the values of either. Any
* undefined fields in <var>delta</var> are ignored.
@@ -759,6 +810,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(compatScreenWidthDp);
dest.writeInt(compatScreenHeightDp);
dest.writeInt(compatSmallestScreenWidthDp);
+ dest.writeInt(layoutDirection);
dest.writeInt(seq);
}
@@ -786,6 +838,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatScreenWidthDp = source.readInt();
compatScreenHeightDp = source.readInt();
compatSmallestScreenWidthDp = source.readInt();
+ layoutDirection = source.readInt();
seq = source.readInt();
}
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 23a6f97..63e33ce 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -18,7 +18,6 @@ package android.content.res;
import android.text.*;
import android.text.style.*;
-import android.util.Config;
import android.util.Log;
import android.util.SparseArray;
import android.graphics.Paint;
@@ -34,7 +33,7 @@ import com.android.internal.util.XmlUtils;
*/
final class StringBlock {
private static final String TAG = "AssetManager";
- private static final boolean localLOGV = Config.LOGV || false;
+ private static final boolean localLOGV = false || false;
private final int mNative;
private final boolean mUseSparse;
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 3ffc714..b6487bd 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -19,7 +19,6 @@ package android.database;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
-import android.util.Config;
import android.util.Log;
import java.lang.ref.WeakReference;
@@ -285,7 +284,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
}
- if (Config.LOGV) {
+ if (false) {
if (getCount() > 0) {
Log.w("AbstractCursor", "Unknown column " + columnName);
}
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 8bc7de2..8fa4d3b 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -19,7 +19,6 @@ package android.database;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Config;
import android.util.Log;
@@ -77,7 +76,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
if (mCursor instanceof AbstractWindowedCursor) {
AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
if (windowedCursor.hasWindow()) {
- if (Log.isLoggable(TAG, Log.VERBOSE) || Config.LOGV) {
+ if (Log.isLoggable(TAG, Log.VERBOSE) || false) {
Log.v(TAG, "Cross process cursor has a local window before setWindow in "
+ providerName, new RuntimeException());
}
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index f428aad..8e6f699 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -33,7 +33,6 @@ import android.database.sqlite.SQLiteStatement;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import java.io.FileNotFoundException;
@@ -49,7 +48,7 @@ public class DatabaseUtils {
private static final String TAG = "DatabaseUtils";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final String[] countProjection = new String[]{"count(*)"};
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 4c2d123..ea9346d 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -23,7 +23,6 @@ import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.StrictMode;
-import android.util.Config;
import android.util.Log;
import java.util.HashMap;
@@ -241,7 +240,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
mColumnNameMap = null;
mQuery = query;
- query.mDatabase.lock();
+ query.mDatabase.lock(query.mSql);
try {
// Setup the list of columns
int columnCount = mQuery.columnCountLocked();
@@ -251,7 +250,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
for (int i = 0; i < columnCount; i++) {
String columnName = mQuery.columnNameLocked(i);
mColumns[i] = columnName;
- if (Config.LOGV) {
+ if (false) {
Log.v("DatabaseWindow", "mColumns[" + i + "] is "
+ mColumns[i]);
}
@@ -366,13 +365,13 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
private void deactivateCommon() {
- if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
+ if (false) Log.v(TAG, "<<< Releasing cursor " + this);
mCursorState = 0;
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
- if (Config.LOGV) Log.v("DatabaseWindow", "closing window in release()");
+ if (false) Log.v("DatabaseWindow", "closing window in release()");
}
@Override
@@ -398,7 +397,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
return false;
}
long timeStart = 0;
- if (Config.LOGV) {
+ if (false) {
timeStart = System.currentTimeMillis();
}
@@ -419,7 +418,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
// since we need to use a different database connection handle,
// re-compile the query
try {
- db.lock();
+ db.lock(mQuery.mSql);
} catch (IllegalStateException e) {
// for backwards compatibility, just return false
Log.w(TAG, "requery() failed " + e.getMessage(), e);
@@ -453,7 +452,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
}
- if (Config.LOGV) {
+ if (false) {
Log.v("DatabaseWindow", "closing window in requery()");
Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
}
@@ -465,7 +464,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
// for backwards compatibility, just return false
Log.w(TAG, "requery() failed " + e.getMessage(), e);
}
- if (Config.LOGV) {
+ if (false) {
long timeEnd = System.currentTimeMillis();
Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
}
@@ -513,7 +512,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
close();
SQLiteDebug.notifyActiveCursorFinalized();
} else {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Finalizing cursor on database = " + mQuery.mDatabase.getPath() +
", table = " + mEditTable + ", query = " + mQuery.mSql);
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 90a5b5d..93a6ad3 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -30,7 +30,6 @@ import android.os.StatFs;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import android.util.LruCache;
@@ -230,9 +229,23 @@ public class SQLiteDatabase extends SQLiteClosable {
private static int sQueryLogTimeInMillis = 0; // lazily initialized
private static final int QUERY_LOG_SQL_LENGTH = 64;
private static final String COMMIT_SQL = "COMMIT;";
+ private static final String BEGIN_SQL = "BEGIN;";
private final Random mRandom = new Random();
+ /** the last non-commit/rollback sql statement in a transaction */
+ // guarded by 'this'
private String mLastSqlStatement = null;
+ synchronized String getLastSqlStatement() {
+ return mLastSqlStatement;
+ }
+
+ synchronized void setLastSqlStatement(String sql) {
+ mLastSqlStatement = sql;
+ }
+
+ /** guarded by {@link #mLock} */
+ private long mTransStartTime;
+
// String prefix for slow database query EventLog records that show
// lock acquistions of the database.
/* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:";
@@ -386,11 +399,16 @@ public class SQLiteDatabase extends SQLiteClosable {
*
* @see #unlock()
*/
- /* package */ void lock() {
- lock(false);
+ /* package */ void lock(String sql) {
+ lock(sql, false);
+ }
+
+ /* pachage */ void lock() {
+ lock(null, false);
}
+
private static final long LOCK_WAIT_PERIOD = 30L;
- private void lock(boolean forced) {
+ private void lock(String sql, boolean forced) {
// make sure this method is NOT being called from a 'synchronized' method
if (Thread.holdsLock(this)) {
Log.w(TAG, "don't lock() while in a synchronized method");
@@ -398,6 +416,7 @@ public class SQLiteDatabase extends SQLiteClosable {
verifyDbIsOpen();
if (!forced && !mLockingEnabled) return;
boolean done = false;
+ long timeStart = SystemClock.uptimeMillis();
while (!done) {
try {
// wait for 30sec to acquire the lock
@@ -420,6 +439,9 @@ public class SQLiteDatabase extends SQLiteClosable {
mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
}
}
+ if (sql != null) {
+ logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX);
+ }
}
private static class DatabaseReentrantLock extends ReentrantLock {
DatabaseReentrantLock(boolean fair) {
@@ -444,7 +466,11 @@ public class SQLiteDatabase extends SQLiteClosable {
* @see #unlockForced()
*/
private void lockForced() {
- lock(true);
+ lock(null, true);
+ }
+
+ private void lockForced(String sql) {
+ lock(sql, true);
}
/**
@@ -612,7 +638,7 @@ public class SQLiteDatabase extends SQLiteClosable {
private void beginTransaction(SQLiteTransactionListener transactionListener,
boolean exclusive) {
verifyDbIsOpen();
- lockForced();
+ lockForced(BEGIN_SQL);
boolean ok = false;
try {
// If this thread already had the lock then get out
@@ -635,6 +661,7 @@ public class SQLiteDatabase extends SQLiteClosable {
} else {
execSQL("BEGIN IMMEDIATE;");
}
+ mTransStartTime = SystemClock.uptimeMillis();
mTransactionListener = transactionListener;
mTransactionIsSuccessful = true;
mInnerTransactionIsSuccessful = false;
@@ -698,6 +725,8 @@ public class SQLiteDatabase extends SQLiteClosable {
Log.i(TAG, "PRAGMA wal_Checkpoint done");
}
}
+ // log the transaction time to the Eventlog.
+ logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL);
} else {
try {
execSQL("ROLLBACK;");
@@ -705,7 +734,7 @@ public class SQLiteDatabase extends SQLiteClosable {
throw savedException;
}
} catch (SQLException e) {
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "exception during rollback, maybe the DB previously "
+ "performed an auto-rollback");
}
@@ -714,7 +743,7 @@ public class SQLiteDatabase extends SQLiteClosable {
} finally {
mTransactionListener = null;
unlockForced();
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "unlocked " + Thread.currentThread()
+ ", holdCount is " + mLock.getHoldCount());
}
@@ -1527,7 +1556,7 @@ public class SQLiteDatabase extends SQLiteClosable {
BlockGuard.getThreadPolicy().onReadFromDisk();
long timeStart = 0;
- if (Config.LOGV || mSlowQueryThreshold != -1) {
+ if (false || mSlowQueryThreshold != -1) {
timeStart = System.currentTimeMillis();
}
@@ -1540,7 +1569,7 @@ public class SQLiteDatabase extends SQLiteClosable {
cursorFactory != null ? cursorFactory : mFactory,
selectionArgs);
} finally {
- if (Config.LOGV || mSlowQueryThreshold != -1) {
+ if (false || mSlowQueryThreshold != -1) {
// Force query execution
int count = -1;
@@ -1550,7 +1579,7 @@ public class SQLiteDatabase extends SQLiteClosable {
long duration = System.currentTimeMillis() - timeStart;
- if (Config.LOGV || duration >= mSlowQueryThreshold) {
+ if (false || duration >= mSlowQueryThreshold) {
Log.v(SQLiteCursor.TAG,
"query (" + duration + " ms): " + driver.toString() + ", args are "
+ (selectionArgs != null
@@ -1855,24 +1884,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException if the SQL string is invalid
*/
public void execSQL(String sql) throws SQLException {
- int stmtType = DatabaseUtils.getSqlStatementType(sql);
- if (stmtType == DatabaseUtils.STATEMENT_ATTACH) {
- disableWriteAheadLogging();
- }
- long timeStart = SystemClock.uptimeMillis();
- logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX);
executeSql(sql, null);
-
- if (stmtType == DatabaseUtils.STATEMENT_ATTACH) {
- mHasAttachedDbs = true;
- }
- // Log commit statements along with the most recently executed
- // SQL statement for disambiguation.
- if (stmtType == DatabaseUtils.STATEMENT_COMMIT) {
- logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL);
- } else {
- logTimeStat(sql, timeStart, null);
- }
}
/**
@@ -1926,19 +1938,19 @@ public class SQLiteDatabase extends SQLiteClosable {
}
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
- long timeStart = SystemClock.uptimeMillis();
- int n;
+ if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+ disableWriteAheadLogging();
+ mHasAttachedDbs = true;
+ }
SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
try {
- n = statement.executeUpdateDelete();
+ return statement.executeUpdateDelete();
} catch (SQLiteDatabaseCorruptException e) {
onCorruption();
throw e;
} finally {
statement.close();
}
- logTimeStat(sql, timeStart);
- return n;
}
@Override
@@ -2027,12 +2039,7 @@ public class SQLiteDatabase extends SQLiteClosable {
logTimeStat(sql, beginMillis, null);
}
- /* package */ void logTimeStat(String sql, long beginMillis, String prefix) {
- // Keep track of the last statement executed here, as this is
- // the common funnel through which all methods of hitting
- // libsqlite eventually flow.
- mLastSqlStatement = sql;
-
+ private void logTimeStat(String sql, long beginMillis, String prefix) {
// Sample fast queries in proportion to the time taken.
// Quantize the % first, so the logged sampling probability
// exactly equals the actual sampling rate for this query.
@@ -2059,7 +2066,6 @@ public class SQLiteDatabase extends SQLiteClosable {
if (prefix != null) {
sql = prefix + sql;
}
-
if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH);
// ActivityThread.currentPackageName() only returns non-null if the
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index de2fca9..a5e762e 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -42,7 +42,7 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
SQLiteQuery query = null;
try {
- mDatabase.lock();
+ mDatabase.lock(mSql);
mDatabase.closePendingStatements();
query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 88246e8..89552dc 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -105,12 +105,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
case DatabaseUtils.STATEMENT_SELECT:
mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN;
break;
- case DatabaseUtils.STATEMENT_ATTACH:
case DatabaseUtils.STATEMENT_BEGIN:
case DatabaseUtils.STATEMENT_COMMIT:
case DatabaseUtils.STATEMENT_ABORT:
- case DatabaseUtils.STATEMENT_DDL:
- case DatabaseUtils.STATEMENT_UNPREPARED:
mStatementType = n | STATEMENT_DONT_PREPARE;
break;
default:
@@ -353,13 +350,10 @@ public abstract class SQLiteProgram extends SQLiteClosable {
/* package */ void compileAndbindAllArgs() {
if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
- // no need to prepare this SQL statement
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- if (mBindArgs != null) {
- throw new IllegalArgumentException("no need to pass bindargs for this sql :" +
- mSql);
- }
+ if (mBindArgs != null) {
+ throw new IllegalArgumentException("Can't pass bindargs for this sql :" + mSql);
}
+ // no need to prepare this SQL statement
return;
}
if (nStatement == 0) {
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index e9e0172..dc882d9 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -70,9 +70,8 @@ public class SQLiteQuery extends SQLiteProgram {
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
+ mDatabase.lock(mSql);
long timeStart = SystemClock.uptimeMillis();
- mDatabase.lock();
- mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
try {
acquireReference();
try {
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index b6aca2b..8f8eb6e 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -24,8 +24,8 @@ import android.util.Log;
import java.util.Iterator;
import java.util.Map;
-import java.util.Set;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -43,7 +43,7 @@ public class SQLiteQueryBuilder
private StringBuilder mWhereClause = null; // lazily created
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
- private boolean mStrictProjectionMap;
+ private boolean mStrict;
public SQLiteQueryBuilder() {
mDistinct = false;
@@ -145,10 +145,28 @@ public class SQLiteQueryBuilder
}
/**
- * @hide
+ * When set, the selection is verified against malicious arguments.
+ * When using this class to create a statement using
+ * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
+ * non-numeric limits will raise an exception. If a projection map is specified, fields
+ * not in that map will be ignored.
+ * If this class is used to execute the statement directly using
+ * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
+ * or
+ * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
+ * additionally also parenthesis escaping selection are caught.
+ *
+ * To summarize: To get maximum protection against malicious third party apps (for example
+ * content provider consumers), make sure to do the following:
+ * <ul>
+ * <li>Set this value to true</li>
+ * <li>Use a projection map</li>
+ * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
+ * </ul>
+ * By default, this value is false.
*/
- public void setStrictProjectionMap(boolean flag) {
- mStrictProjectionMap = flag;
+ public void setStrict(boolean flag) {
+ mStrict = flag;
}
/**
@@ -217,13 +235,6 @@ public class SQLiteQueryBuilder
}
}
- private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) {
- if (!TextUtils.isEmpty(clause)) {
- s.append(name);
- DatabaseUtils.appendEscapedSQLString(s, clause);
- }
- }
-
/**
* Add the names that are non-null in columns to s, separating
* them with commas.
@@ -320,6 +331,19 @@ public class SQLiteQueryBuilder
return null;
}
+ if (mStrict && selection != null && selection.length() > 0) {
+ // Validate the user-supplied selection to detect syntactic anomalies
+ // in the selection string that could indicate a SQL injection attempt.
+ // The idea is to ensure that the selection clause is a valid SQL expression
+ // by compiling it twice: once wrapped in parentheses and once as
+ // originally specified. An attacker cannot create an expression that
+ // would escape the SQL expression while maintaining balanced parentheses
+ // in both the wrapped and original forms.
+ String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
+ having, sortOrder, limit);
+ validateSql(db, sqlForValidation); // will throw if query is invalid
+ }
+
String sql = buildQuery(
projectionIn, selection, groupBy, having,
sortOrder, limit);
@@ -329,7 +353,20 @@ public class SQLiteQueryBuilder
}
return db.rawQueryWithFactory(
mFactory, sql, selectionArgs,
- SQLiteDatabase.findEditTable(mTables));
+ SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid
+ }
+
+ /**
+ * Verifies that a SQL statement is valid by compiling it.
+ * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
+ */
+ private void validateSql(SQLiteDatabase db, String sql) {
+ db.lock(sql);
+ try {
+ new SQLiteCompiledSql(db, sql).releaseSqlStatement();
+ } finally {
+ db.unlock();
+ }
}
/**
@@ -541,7 +578,7 @@ public class SQLiteQueryBuilder
continue;
}
- if (!mStrictProjectionMap &&
+ if (!mStrict &&
( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index c76cc6c..ff973a7 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -80,7 +80,8 @@ public class SQLiteStatement extends SQLiteProgram
*/
public int executeUpdateDelete() {
try {
- long timeStart = acquireAndLock(WRITE);
+ saveSqlAsLastSqlStatement();
+ acquireAndLock(WRITE);
int numChanges = 0;
if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
// since the statement doesn't have to be prepared,
@@ -90,7 +91,6 @@ public class SQLiteStatement extends SQLiteProgram
} else {
numChanges = native_execute();
}
- mDatabase.logTimeStat(mSql, timeStart);
return numChanges;
} finally {
releaseAndUnlock();
@@ -108,15 +108,22 @@ public class SQLiteStatement extends SQLiteProgram
*/
public long executeInsert() {
try {
- long timeStart = acquireAndLock(WRITE);
- long lastInsertedRowId = native_executeInsert();
- mDatabase.logTimeStat(mSql, timeStart);
- return lastInsertedRowId;
+ saveSqlAsLastSqlStatement();
+ acquireAndLock(WRITE);
+ return native_executeInsert();
} finally {
releaseAndUnlock();
}
}
+ private void saveSqlAsLastSqlStatement() {
+ if (((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+ DatabaseUtils.STATEMENT_UPDATE) ||
+ (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+ DatabaseUtils.STATEMENT_BEGIN) {
+ mDatabase.setLastSqlStatement(mSql);
+ }
+ }
/**
* Execute a statement that returns a 1 by 1 table with a numeric value.
* For example, SELECT COUNT(*) FROM table;
@@ -199,7 +206,7 @@ public class SQLiteStatement extends SQLiteProgram
* <li>if the SQL statement is an update, start transaction if not already in one.
* otherwise, get lock on the database</li>
* <li>acquire reference on this object</li>
- * <li>and then return the current time _before_ the database lock was acquired</li>
+ * <li>and then return the current time _after_ the database lock was acquired</li>
* </ul>
* <p>
* This method removes the duplicate code from the other public
@@ -243,7 +250,7 @@ public class SQLiteStatement extends SQLiteProgram
}
// do I have database lock? if not, grab it.
if (!mDatabase.isDbLockedByCurrentThread()) {
- mDatabase.lock();
+ mDatabase.lock(mSql);
mState = LOCK_ACQUIRED;
}
diff --git a/core/java/android/ddm/DdmHandleAppName.java b/core/java/android/ddm/DdmHandleAppName.java
index 4a57d12..78dd23e 100644
--- a/core/java/android/ddm/DdmHandleAppName.java
+++ b/core/java/android/ddm/DdmHandleAppName.java
@@ -19,7 +19,6 @@ package android.ddm;
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -88,7 +87,7 @@ public class DdmHandleAppName extends ChunkHandler {
* Send an APNM (APplication NaMe) chunk.
*/
private static void sendAPNM(String appName) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm", "Sending app name");
ByteBuffer out = ByteBuffer.allocate(4 + appName.length()*2);
diff --git a/core/java/android/ddm/DdmHandleExit.java b/core/java/android/ddm/DdmHandleExit.java
index 8a0b9a4..74ae37a 100644
--- a/core/java/android/ddm/DdmHandleExit.java
+++ b/core/java/android/ddm/DdmHandleExit.java
@@ -19,7 +19,6 @@ package android.ddm;
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -59,7 +58,7 @@ public class DdmHandleExit extends ChunkHandler {
* Handle a chunk of data. We're only registered for "EXIT".
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-exit", "Handling " + name(request.type) + " chunk");
/*
diff --git a/core/java/android/ddm/DdmHandleHeap.java b/core/java/android/ddm/DdmHandleHeap.java
index fa0fbbf..cece556 100644
--- a/core/java/android/ddm/DdmHandleHeap.java
+++ b/core/java/android/ddm/DdmHandleHeap.java
@@ -21,7 +21,6 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
import android.os.Debug;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -78,7 +77,7 @@ public class DdmHandleHeap extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Handling " + name(request.type) + " chunk");
int type = request.type;
@@ -113,7 +112,7 @@ public class DdmHandleHeap extends ChunkHandler {
ByteBuffer in = wrapChunk(request);
int when = in.get();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Heap segment enable: when=" + when);
boolean ok = DdmVmInternal.heapInfoNotify(when);
@@ -132,7 +131,7 @@ public class DdmHandleHeap extends ChunkHandler {
int when = in.get();
int what = in.get();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Heap segment enable: when=" + when
+ ", what=" + what + ", isNative=" + isNative);
@@ -160,7 +159,7 @@ public class DdmHandleHeap extends ChunkHandler {
/* get the filename for the output file */
int len = in.getInt();
String fileName = getString(in, len);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Heap dump: file='" + fileName + "'");
try {
@@ -192,7 +191,7 @@ public class DdmHandleHeap extends ChunkHandler {
byte result;
/* get the filename for the output file */
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Heap dump: [DDMS]");
String failMsg = null;
@@ -218,7 +217,7 @@ public class DdmHandleHeap extends ChunkHandler {
private Chunk handleHPGC(Chunk request) {
//ByteBuffer in = wrapChunk(request);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Heap GC request");
System.gc();
@@ -234,7 +233,7 @@ public class DdmHandleHeap extends ChunkHandler {
enable = (in.get() != 0);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Recent allocation enable request: " + enable);
DdmVmInternal.enableRecentAllocations(enable);
@@ -259,7 +258,7 @@ public class DdmHandleHeap extends ChunkHandler {
private Chunk handleREAL(Chunk request) {
//ByteBuffer in = wrapChunk(request);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Recent allocations request");
/* generate the reply in a ready-to-go format */
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 714a611..5088d22 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -19,7 +19,6 @@ package android.ddm;
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
import android.os.Debug;
@@ -53,7 +52,7 @@ public class DdmHandleHello extends ChunkHandler {
* send messages to the server.
*/
public void connected() {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-hello", "Connected!");
if (false) {
@@ -70,7 +69,7 @@ public class DdmHandleHello extends ChunkHandler {
* periodic transmissions or clean up saved state.
*/
public void disconnected() {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-hello", "Disconnected!");
}
@@ -78,7 +77,7 @@ public class DdmHandleHello extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Handling " + name(request.type) + " chunk");
int type = request.type;
@@ -105,7 +104,7 @@ public class DdmHandleHello extends ChunkHandler {
ByteBuffer in = wrapChunk(request);
int serverProtoVers = in.getInt();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-hello", "Server version is " + serverProtoVers);
/*
@@ -150,7 +149,7 @@ public class DdmHandleHello extends ChunkHandler {
// is actually compiled in
final String[] features = Debug.getVmFeatureList();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Got feature list request");
int size = 4 + 4 * features.length;
diff --git a/core/java/android/ddm/DdmHandleProfiling.java b/core/java/android/ddm/DdmHandleProfiling.java
index 63ee445..e0db5e7 100644
--- a/core/java/android/ddm/DdmHandleProfiling.java
+++ b/core/java/android/ddm/DdmHandleProfiling.java
@@ -20,7 +20,6 @@ import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import android.os.Debug;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -69,7 +68,7 @@ public class DdmHandleProfiling extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Handling " + name(request.type) + " chunk");
int type = request.type;
@@ -99,7 +98,7 @@ public class DdmHandleProfiling extends ChunkHandler {
int flags = in.getInt();
int len = in.getInt();
String fileName = getString(in, len);
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Method profiling start: filename='" + fileName
+ "', size=" + bufferSize + ", flags=" + flags);
@@ -139,7 +138,7 @@ public class DdmHandleProfiling extends ChunkHandler {
int bufferSize = in.getInt();
int flags = in.getInt();
- if (Config.LOGV) {
+ if (false) {
Log.v("ddm-heap", "Method prof stream start: size=" + bufferSize
+ ", flags=" + flags);
}
@@ -158,7 +157,7 @@ public class DdmHandleProfiling extends ChunkHandler {
private Chunk handleMPSE(Chunk request) {
byte result;
- if (Config.LOGV) {
+ if (false) {
Log.v("ddm-heap", "Method prof stream end");
}
diff --git a/core/java/android/ddm/DdmHandleThread.java b/core/java/android/ddm/DdmHandleThread.java
index c307988..613ab75 100644
--- a/core/java/android/ddm/DdmHandleThread.java
+++ b/core/java/android/ddm/DdmHandleThread.java
@@ -20,7 +20,6 @@ import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
-import android.util.Config;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -66,7 +65,7 @@ public class DdmHandleThread extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-thread", "Handling " + name(request.type) + " chunk");
int type = request.type;
diff --git a/core/java/android/ddm/DdmRegister.java b/core/java/android/ddm/DdmRegister.java
index debf189..ecd450d 100644
--- a/core/java/android/ddm/DdmRegister.java
+++ b/core/java/android/ddm/DdmRegister.java
@@ -17,7 +17,6 @@
package android.ddm;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
/**
@@ -44,7 +43,7 @@ public class DdmRegister {
* we finish here.
*/
public static void registerHandlers() {
- if (Config.LOGV)
+ if (false)
Log.v("ddm", "Registering DDM message handlers");
DdmHandleHello.register();
DdmHandleThread.register();
diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java
index 300cd28..c6a2a87 100755
--- a/core/java/android/gesture/Gesture.java
+++ b/core/java/android/gesture/Gesture.java
@@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes.
* Each stroke is a sequence of timed points. A user-defined gesture can be recognized by
- * a GestureLibrary and a built-in alphabet gesture can be recognized by a LetterRecognizer.
+ * a GestureLibrary.
*/
public class Gesture implements Parcelable {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 97f0e1b..1df3108 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -16,21 +16,22 @@
package android.hardware;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.StringTokenizer;
-import java.io.IOException;
-
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
import android.graphics.ImageFormat;
+import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringTokenizer;
/**
* The Camera class is used to set image capture settings, start/stop preview,
@@ -172,16 +173,16 @@ public class Camera {
public int facing;
/**
- * The orientation of the camera image. The value is the angle that the
+ * <p>The orientation of the camera image. The value is the angle that the
* camera image needs to be rotated clockwise so it shows correctly on
- * the display in its natural orientation. It should be 0, 90, 180, or 270.
+ * the display in its natural orientation. It should be 0, 90, 180, or 270.</p>
*
- * For example, suppose a device has a naturally tall screen. The
+ * <p>For example, suppose a device has a naturally tall screen. The
* back-facing camera sensor is mounted in landscape. You are looking at
* the screen. If the top side of the camera sensor is aligned with the
* right edge of the screen in natural orientation, the value should be
* 90. If the top side of a front-facing camera sensor is aligned with
- * the right of the screen, the value should be 270.
+ * the right of the screen, the value should be 270.</p>
*
* @see #setDisplayOrientation(int)
* @see Parameters#setRotation(int)
@@ -374,6 +375,12 @@ public class Camera {
* The preview surface texture may not otherwise change while preview is
* running.
*
+ * <p>The timestamps provided by {@link SurfaceTexture#getTimestamp()} for a
+ * SurfaceTexture set as the preview texture have an unspecified zero point,
+ * and cannot be directly compared between different cameras or different
+ * instances of the same camera, or across multiple runs of the same
+ * program.
+ *
* @param surfaceTexture the {@link SurfaceTexture} to which the preview
* images are to be sent or null to remove the current preview surface
* texture
@@ -410,8 +417,9 @@ public class Camera {
/**
* Starts capturing and drawing preview frames to the screen.
- * Preview will not actually start until a surface is supplied with
- * {@link #setPreviewDisplay(SurfaceHolder)}.
+ * Preview will not actually start until a surface is supplied
+ * with {@link #setPreviewDisplay(SurfaceHolder)} or
+ * {@link #setPreviewTexture(SurfaceTexture)}.
*
* <p>If {@link #setPreviewCallback(Camera.PreviewCallback)},
* {@link #setOneShotPreviewCallback(Camera.PreviewCallback)}, or
@@ -553,12 +561,12 @@ public class Camera {
* is used while calling {@link #takePicture(Camera.ShutterCallback,
* Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)}.
*
- * Please note that by calling this method, the mode for application-managed
- * callback buffers is triggered. If this method has never been called,
- * null will be returned by the raw image callback since there is
- * no image callback buffer available. Furthermore, When a supplied buffer
- * is too small to hold the raw image data, raw image callback will return
- * null and the buffer will be removed from the buffer queue.
+ * <p>Please note that by calling this method, the mode for
+ * application-managed callback buffers is triggered. If this method has
+ * never been called, null will be returned by the raw image callback since
+ * there is no image callback buffer available. Furthermore, When a supplied
+ * buffer is too small to hold the raw image data, raw image callback will
+ * return null and the buffer will be removed from the buffer queue.
*
* @param callbackBuffer the buffer to add to the raw image callback buffer
* queue. The size should be width * height * (bits per pixel) / 8. An
@@ -826,8 +834,6 @@ public class Camera {
* @param raw the callback for raw (uncompressed) image data, or null
* @param postview callback with postview image data, may be null
* @param jpeg the callback for JPEG image data, or null
- *
- * @see #addRawImageCallbackBuffer(byte[])
*/
public final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback postview, PictureCallback jpeg) {
@@ -1076,6 +1082,94 @@ public class Camera {
};
/**
+ * <p>The Area class is used for choosing specific metering and focus areas for
+ * the camera to use when calculating auto-exposure, auto-white balance, and
+ * auto-focus.</p>
+ *
+ * <p>To find out how many simultaneous areas a given camera supports, use
+ * {@link Parameters#getMaxNumMeteringAreas()} and
+ * {@link Parameters#getMaxNumFocusAreas()}. If metering or focusing area
+ * selection is unsupported, these methods will return 0.</p>
+ *
+ * <p>Each Area consists of a rectangle specifying its bounds, and a weight
+ * that determines its importance. The bounds are relative to the camera's
+ * current field of view. The coordinates are mapped so that (-1000, -1000)
+ * is always the top-left corner of the current field of view, and (1000,
+ * 1000) is always the bottom-right corner of the current field of
+ * view. Setting Areas with bounds outside that range is not allowed. Areas
+ * with zero or negative width or height are not allowed.</p>
+ *
+ * <p>The weight must range from 1 to 1000, and represents a weight for
+ * every pixel in the area. This means that a large metering area with
+ * the same weight as a smaller area will have more effect in the
+ * metering result. Metering areas can overlap and the driver
+ * will add the weights in the overlap region.</p>
+ *
+ * @see Parameters#setFocusAreas(List)
+ * @see Parameters#getFocusAreas()
+ * @see Parameters#getMaxNumFocusAreas()
+ * @see Parameters#setMeteringAreas(List)
+ * @see Parameters#getMeteringAreas()
+ * @see Parameters#getMaxNumMeteringAreas()
+ */
+ public static class Area {
+ /**
+ * Create an area with specified rectangle and weight.
+ *
+ * @param rect the bounds of the area.
+ * @param weight the weight of the area.
+ */
+ public Area(Rect rect, int weight) {
+ this.rect = rect;
+ this.weight = weight;
+ }
+ /**
+ * Compares {@code obj} to this area.
+ *
+ * @param obj the object to compare this area with.
+ * @return {@code true} if the rectangle and weight of {@code obj} is
+ * the same as those of this area. {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Area)) {
+ return false;
+ }
+ Area a = (Area) obj;
+ if (rect == null) {
+ if (a.rect != null) return false;
+ } else {
+ if (!rect.equals(a.rect)) return false;
+ }
+ return weight == a.weight;
+ }
+
+ /**
+ * Bounds of the area. (-1000, -1000) represents the top-left of the
+ * camera field of view, and (1000, 1000) represents the bottom-right of
+ * the field of view. Setting bounds outside that range is not
+ * allowed. Bounds with zero or negative width or height are not
+ * allowed.
+ *
+ * @see Parameters#getFocusAreas()
+ * @see Parameters#getMeteringAreas()
+ */
+ public Rect rect;
+
+ /**
+ * Weight of the area. The weight must range from 1 to 1000, and
+ * represents a weight for every pixel in the area. This means that a
+ * large metering area with the same weight as a smaller area will have
+ * more effect in the metering result. Metering areas can overlap and
+ * the driver will add the weights in the overlap region.
+ *
+ * @see Parameters#getFocusAreas()
+ * @see Parameters#getMeteringAreas()
+ */
+ public int weight;
+ }
+
+ /**
* Camera service settings.
*
* <p>To make camera parameters take effect, applications have to call
@@ -1117,6 +1211,8 @@ public class Camera {
private static final String KEY_SCENE_MODE = "scene-mode";
private static final String KEY_FLASH_MODE = "flash-mode";
private static final String KEY_FOCUS_MODE = "focus-mode";
+ private static final String KEY_FOCUS_AREAS = "focus-areas";
+ private static final String KEY_MAX_NUM_FOCUS_AREAS = "max-num-focus-areas";
private static final String KEY_FOCAL_LENGTH = "focal-length";
private static final String KEY_HORIZONTAL_VIEW_ANGLE = "horizontal-view-angle";
private static final String KEY_VERTICAL_VIEW_ANGLE = "vertical-view-angle";
@@ -1124,6 +1220,12 @@ public class Camera {
private static final String KEY_MAX_EXPOSURE_COMPENSATION = "max-exposure-compensation";
private static final String KEY_MIN_EXPOSURE_COMPENSATION = "min-exposure-compensation";
private static final String KEY_EXPOSURE_COMPENSATION_STEP = "exposure-compensation-step";
+ private static final String KEY_AUTO_EXPOSURE_LOCK = "auto-exposure-lock";
+ private static final String KEY_AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
+ private static final String KEY_AUTO_WHITEBALANCE_LOCK = "auto-whitebalance-lock";
+ private static final String KEY_AUTO_WHITEBALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
+ private static final String KEY_METERING_AREAS = "metering-areas";
+ private static final String KEY_MAX_NUM_METERING_AREAS = "max-num-metering-areas";
private static final String KEY_ZOOM = "zoom";
private static final String KEY_MAX_ZOOM = "max-zoom";
private static final String KEY_ZOOM_RATIOS = "zoom-ratios";
@@ -1138,6 +1240,7 @@ public class Camera {
private static final String SUPPORTED_VALUES_SUFFIX = "-values";
private static final String TRUE = "true";
+ private static final String FALSE = "false";
// Values for white balance settings.
public static final String WHITE_BALANCE_AUTO = "auto";
@@ -1462,6 +1565,31 @@ public class Camera {
mMap.put(key, Integer.toString(value));
}
+ private void set(String key, List<Area> areas) {
+ if (areas == null) {
+ set(key, "(0,0,0,0,0)");
+ } else {
+ StringBuilder buffer = new StringBuilder();
+ for (int i = 0; i < areas.size(); i++) {
+ Area area = areas.get(i);
+ Rect rect = area.rect;
+ buffer.append('(');
+ buffer.append(rect.left);
+ buffer.append(',');
+ buffer.append(rect.top);
+ buffer.append(',');
+ buffer.append(rect.right);
+ buffer.append(',');
+ buffer.append(rect.bottom);
+ buffer.append(',');
+ buffer.append(area.weight);
+ buffer.append(')');
+ if (i != areas.size() - 1) buffer.append(',');
+ }
+ set(key, buffer.toString());
+ }
+ }
+
/**
* Returns the value of a String parameter.
*
@@ -1483,7 +1611,9 @@ public class Camera {
}
/**
- * Sets the dimensions for preview pictures.
+ * Sets the dimensions for preview pictures. If the preview has already
+ * started, applications should stop the preview first before changing
+ * preview size.
*
* The sides of width and height are based on camera orientation. That
* is, the preview size is the size before it is rotated by display
@@ -1531,15 +1661,15 @@ public class Camera {
}
/**
- * Gets the supported video frame sizes that can be used by
- * MediaRecorder.
+ * <p>Gets the supported video frame sizes that can be used by
+ * MediaRecorder.</p>
*
- * If the returned list is not null, the returned list will contain at
+ * <p>If the returned list is not null, the returned list will contain at
* least one Size and one of the sizes in the returned list must be
* passed to MediaRecorder.setVideoSize() for camcorder application if
* camera is used as the video source. In this case, the size of the
* preview can be different from the resolution of the recorded video
- * during video recording.
+ * during video recording.</p>
*
* @return a list of Size object if camera has separate preview and
* video output; otherwise, null is returned.
@@ -1571,12 +1701,12 @@ public class Camera {
}
/**
- * Sets the dimensions for EXIF thumbnail in Jpeg picture. If
+ * <p>Sets the dimensions for EXIF thumbnail in Jpeg picture. If
* applications set both width and height to 0, EXIF will not contain
- * thumbnail.
+ * thumbnail.</p>
*
- * Applications need to consider the display orientation. See {@link
- * #setPreviewSize(int,int)} for reference.
+ * <p>Applications need to consider the display orientation. See {@link
+ * #setPreviewSize(int,int)} for reference.</p>
*
* @param width the width of the thumbnail, in pixels
* @param height the height of the thumbnail, in pixels
@@ -1796,10 +1926,10 @@ public class Camera {
}
/**
- * Sets the dimensions for pictures.
+ * <p>Sets the dimensions for pictures.</p>
*
- * Applications need to consider the display orientation. See {@link
- * #setPreviewSize(int,int)} for reference.
+ * <p>Applications need to consider the display orientation. See {@link
+ * #setPreviewSize(int,int)} for reference.</p>
*
* @param width the width for pictures, in pixels
* @param height the height for pictures, in pixels
@@ -2381,6 +2511,175 @@ public class Camera {
}
/**
+ * <p>Sets the auto-exposure lock state. Applications should check
+ * {@link #isAutoExposureLockSupported} before using this method.</p>
+ *
+ * <p>If set to true, the camera auto-exposure routine will immediately
+ * pause until the lock is set to false. Exposure compensation settings
+ * changes will still take effect while auto-exposure is locked.</p>
+ *
+ * <p>If auto-exposure is already locked, setting this to true again has
+ * no effect (the driver will not recalculate exposure values).</p>
+ *
+ * <p>Stopping preview with {@link #stopPreview()}, or triggering still
+ * image capture with {@link #takePicture(Camera.ShutterCallback,
+ * Camera.PictureCallback, Camera.PictureCallback)}, will automatically
+ * set the lock to false. However, the lock can be re-enabled before
+ * preview is re-started to keep the same AE parameters.</p>
+ *
+ * <p>Exposure compensation, in conjunction with re-enabling the AE and
+ * AWB locks after each still capture, can be used to capture an
+ * exposure-bracketed burst of images, for example.</p>
+ *
+ * <p>Auto-exposure state, including the lock state, will not be
+ * maintained after camera {@link #release()} is called. Locking
+ * auto-exposure after {@link #open()} but before the first call to
+ * {@link #startPreview()} will not allow the auto-exposure routine to
+ * run at all, and may result in severely over- or under-exposed
+ * images.</p>
+ *
+ * <p>The driver may also independently lock auto-exposure after
+ * auto-focus completes. If this is undesirable, be sure to always set
+ * the auto-exposure lock to false after the
+ * {@link AutoFocusCallback#onAutoFocus(boolean, Camera)} callback is
+ * received. The {@link #getAutoExposureLock()} method can be used after
+ * the callback to determine if the camera has locked auto-exposure
+ * independently.</p>
+ *
+ * @param toggle new state of the auto-exposure lock. True means that
+ * auto-exposure is locked, false means that the auto-exposure
+ * routine is free to run normally.
+ *
+ * @see #getAutoExposureLock()
+ *
+ * @hide
+ */
+ public void setAutoExposureLock(boolean toggle) {
+ set(KEY_AUTO_EXPOSURE_LOCK, toggle ? TRUE : FALSE);
+ }
+
+ /**
+ * Gets the state of the auto-exposure lock. Applications should check
+ * {@link #isAutoExposureLockSupported} before using this method. See
+ * {@link #setAutoExposureLock} for details about the lock.
+ *
+ * @return State of the auto-exposure lock. Returns true if
+ * auto-exposure is currently locked, and false otherwise. The
+ * auto-exposure lock may be independently enabled by the camera
+ * subsystem when auto-focus has completed. This method can be
+ * used after the {@link AutoFocusCallback#onAutoFocus(boolean,
+ * Camera)} callback to determine if the camera has locked AE.
+ *
+ * @see #setAutoExposureLock(boolean)
+ *
+ * @hide
+ */
+ public boolean getAutoExposureLock() {
+ String str = get(KEY_AUTO_EXPOSURE_LOCK);
+ return TRUE.equals(str);
+ }
+
+ /**
+ * Returns true if auto-exposure locking is supported. Applications
+ * should call this before trying to lock auto-exposure. See
+ * {@link #setAutoExposureLock} for details about the lock.
+ *
+ * @return true if auto-exposure lock is supported.
+ * @see #setAutoExposureLock(boolean)
+ *
+ * @hide
+ */
+ public boolean isAutoExposureLockSupported() {
+ String str = get(KEY_AUTO_EXPOSURE_LOCK_SUPPORTED);
+ return TRUE.equals(str);
+ }
+
+ /**
+ * <p>Sets the auto-white balance lock state. Applications should check
+ * {@link #isAutoWhiteBalanceLockSupported} before using this
+ * method.</p>
+ *
+ * <p>If set to true, the camera auto-white balance routine will
+ * immediately pause until the lock is set to false.</p>
+ *
+ * <p>If auto-white balance is already locked, setting this to true
+ * again has no effect (the driver will not recalculate white balance
+ * values).</p>
+ *
+ * <p>Stopping preview with {@link #stopPreview()}, or triggering still
+ * image capture with {@link #takePicture(Camera.ShutterCallback,
+ * Camera.PictureCallback, Camera.PictureCallback)}, will automatically
+ * set the lock to false. However, the lock can be re-enabled before
+ * preview is re-started to keep the same white balance parameters.</p>
+ *
+ * <p>Exposure compensation, in conjunction with re-enabling the AE and
+ * AWB locks after each still capture, can be used to capture an
+ * exposure-bracketed burst of images, for example. Auto-white balance
+ * state, including the lock state, will not be maintained after camera
+ * {@link #release()} is called. Locking auto-white balance after
+ * {@link #open()} but before the first call to {@link #startPreview()}
+ * will not allow the auto-white balance routine to run at all, and may
+ * result in severely incorrect color in captured images.</p>
+ *
+ * <p>The driver may also independently lock auto-white balance after
+ * auto-focus completes. If this is undesirable, be sure to always set
+ * the auto-white balance lock to false after the
+ * {@link AutoFocusCallback#onAutoFocus(boolean, Camera)} callback is
+ * received. The {@link #getAutoWhiteBalanceLock()} method can be used
+ * after the callback to determine if the camera has locked auto-white
+ * balance independently.</p>
+ *
+ * @param toggle new state of the auto-white balance lock. True means
+ * that auto-white balance is locked, false means that the
+ * auto-white balance routine is free to run normally.
+ *
+ * @see #getAutoWhiteBalanceLock()
+ *
+ * @hide
+ */
+ public void setAutoWhiteBalanceLock(boolean toggle) {
+ set(KEY_AUTO_WHITEBALANCE_LOCK, toggle ? TRUE : FALSE);
+ }
+
+ /**
+ * Gets the state of the auto-white balance lock. Applications should
+ * check {@link #isAutoWhiteBalanceLockSupported} before using this
+ * method. See {@link #setAutoWhiteBalanceLock} for details about the
+ * lock.
+ *
+ * @return State of the auto-white balance lock. Returns true if
+ * auto-white balance is currently locked, and false
+ * otherwise. The auto-white balance lock may be independently
+ * enabled by the camera subsystem when auto-focus has
+ * completed. This method can be used after the
+ * {@link AutoFocusCallback#onAutoFocus(boolean, Camera)}
+ * callback to determine if the camera has locked AWB.
+ *
+ * @see #setAutoWhiteBalanceLock(boolean)
+ *
+ * @hide
+ */
+ public boolean getAutoWhiteBalanceLock() {
+ String str = get(KEY_AUTO_WHITEBALANCE_LOCK);
+ return TRUE.equals(str);
+ }
+
+ /**
+ * Returns true if auto-white balance locking is supported. Applications
+ * should call this before trying to lock auto-white balance. See
+ * {@link #setAutoWhiteBalanceLock} for details about the lock.
+ *
+ * @return true if auto-white balance lock is supported.
+ * @see #setAutoWhiteBalanceLock(boolean)
+ *
+ * @hide
+ */
+ public boolean isAutoWhiteBalanceLockSupported() {
+ String str = get(KEY_AUTO_WHITEBALANCE_LOCK_SUPPORTED);
+ return TRUE.equals(str);
+ }
+
+ /**
* Gets current zoom value. This also works when smooth zoom is in
* progress. Applications should check {@link #isZoomSupported} before
* using this method.
@@ -2456,26 +2755,26 @@ public class Camera {
}
/**
- * Gets the distances from the camera to where an object appears to be
+ * <p>Gets the distances from the camera to where an object appears to be
* in focus. The object is sharpest at the optimal focus distance. The
- * depth of field is the far focus distance minus near focus distance.
+ * depth of field is the far focus distance minus near focus distance.</p>
*
- * Focus distances may change after calling {@link
+ * <p>Focus distances may change after calling {@link
* #autoFocus(AutoFocusCallback)}, {@link #cancelAutoFocus}, or {@link
* #startPreview()}. Applications can call {@link #getParameters()}
* and this method anytime to get the latest focus distances. If the
* focus mode is FOCUS_MODE_CONTINUOUS_VIDEO, focus distances may change
- * from time to time.
+ * from time to time.</p>
*
- * This method is intended to estimate the distance between the camera
+ * <p>This method is intended to estimate the distance between the camera
* and the subject. After autofocus, the subject distance may be within
* near and far focus distance. However, the precision depends on the
* camera hardware, autofocus algorithm, the focus area, and the scene.
- * The error can be large and it should be only used as a reference.
+ * The error can be large and it should be only used as a reference.</p>
*
- * Far focus distance >= optimal focus distance >= near focus distance.
+ * <p>Far focus distance >= optimal focus distance >= near focus distance.
* If the focus distance is infinity, the value will be
- * Float.POSITIVE_INFINITY.
+ * {@code Float.POSITIVE_INFINITY}.</p>
*
* @param output focus distances in meters. output must be a float
* array with three elements. Near focus distance, optimal focus
@@ -2492,6 +2791,140 @@ public class Camera {
splitFloat(get(KEY_FOCUS_DISTANCES), output);
}
+ /**
+ * Gets the maximum number of focus areas supported. This is the maximum
+ * length of the list in {@link #setFocusAreas(List)} and
+ * {@link #getFocusAreas()}.
+ *
+ * @return the maximum number of focus areas supported by the camera.
+ * @see #getFocusAreas()
+ */
+ public int getMaxNumFocusAreas() {
+ return getInt(KEY_MAX_NUM_FOCUS_AREAS, 0);
+ }
+
+ /**
+ * <p>Gets the current focus areas. Camera driver uses the areas to decide
+ * focus.</p>
+ *
+ * <p>Before using this API or {@link #setFocusAreas(List)}, apps should
+ * call {@link #getMaxNumFocusAreas()} to know the maximum number of
+ * focus areas first. If the value is 0, focus area is not supported.</p>
+ *
+ * <p>Each focus area is a rectangle with specified weight. The direction
+ * is relative to the sensor orientation, that is, what the sensor sees.
+ * The direction is not affected by the rotation or mirroring of
+ * {@link #setDisplayOrientation(int)}. Coordinates of the rectangle
+ * range from -1000 to 1000. (-1000, -1000) is the upper left point.
+ * (1000, 1000) is the lower right point. The width and height of focus
+ * areas cannot be 0 or negative.</p>
+ *
+ * <p>The weight must range from 1 to 1000. The weight should be
+ * interpreted as a per-pixel weight - all pixels in the area have the
+ * specified weight. This means a small area with the same weight as a
+ * larger area will have less influence on the focusing than the larger
+ * area. Focus areas can partially overlap and the driver will add the
+ * weights in the overlap region.</p>
+ *
+ * <p>A special case of a {@code null} focus area list means the driver is
+ * free to select focus targets as it wants. For example, the driver may
+ * use more signals to select focus areas and change them
+ * dynamically. Apps can set the focus area list to {@code null} if they
+ * want the driver to completely control focusing.</p>
+ *
+ * <p>Focus areas are relative to the current field of view
+ * ({@link #getZoom()}). No matter what the zoom level is, (-1000,-1000)
+ * represents the top of the currently visible camera frame. The focus
+ * area cannot be set to be outside the current field of view, even
+ * when using zoom.</p>
+ *
+ * <p>Focus area only has effect if the current focus mode is
+ * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, or
+ * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}.</p>
+ *
+ * @return a list of current focus areas
+ */
+ public List<Area> getFocusAreas() {
+ return splitArea(get(KEY_FOCUS_AREAS));
+ }
+
+ /**
+ * Sets focus areas. See {@link #getFocusAreas()} for documentation.
+ *
+ * @param focusAreas the focus areas
+ * @see #getFocusAreas()
+ */
+ public void setFocusAreas(List<Area> focusAreas) {
+ set(KEY_FOCUS_AREAS, focusAreas);
+ }
+
+ /**
+ * Gets the maximum number of metering areas supported. This is the
+ * maximum length of the list in {@link #setMeteringAreas(List)} and
+ * {@link #getMeteringAreas()}.
+ *
+ * @return the maximum number of metering areas supported by the camera.
+ * @see #getMeteringAreas()
+ */
+ public int getMaxNumMeteringAreas() {
+ return getInt(KEY_MAX_NUM_METERING_AREAS, 0);
+ }
+
+ /**
+ * <p>Gets the current metering areas. Camera driver uses these areas to
+ * decide exposure.</p>
+ *
+ * <p>Before using this API or {@link #setMeteringAreas(List)}, apps should
+ * call {@link #getMaxNumMeteringAreas()} to know the maximum number of
+ * metering areas first. If the value is 0, metering area is not
+ * supported.</p>
+ *
+ * <p>Each metering area is a rectangle with specified weight. The
+ * direction is relative to the sensor orientation, that is, what the
+ * sensor sees. The direction is not affected by the rotation or
+ * mirroring of {@link #setDisplayOrientation(int)}. Coordinates of the
+ * rectangle range from -1000 to 1000. (-1000, -1000) is the upper left
+ * point. (1000, 1000) is the lower right point. The width and height of
+ * metering areas cannot be 0 or negative.</p>
+ *
+ * <p>The weight must range from 1 to 1000, and represents a weight for
+ * every pixel in the area. This means that a large metering area with
+ * the same weight as a smaller area will have more effect in the
+ * metering result. Metering areas can partially overlap and the driver
+ * will add the weights in the overlap region.</p>
+ *
+ * <p>A special case of a {@code null} metering area list means the driver
+ * is free to meter as it chooses. For example, the driver may use more
+ * signals to select metering areas and change them dynamically. Apps
+ * can set the metering area list to {@code null} if they want the
+ * driver to completely control metering.</p>
+ *
+ * <p>Metering areas are relative to the current field of view
+ * ({@link #getZoom()}). No matter what the zoom level is, (-1000,-1000)
+ * represents the top of the currently visible camera frame. The
+ * metering area cannot be set to be outside the current field of view,
+ * even when using zoom.</p>
+ *
+ * <p>No matter what metering areas are, the final exposure are compensated
+ * by {@link #setExposureCompensation(int)}.</p>
+ *
+ * @return a list of current metering areas
+ */
+ public List<Area> getMeteringAreas() {
+ return splitArea(KEY_METERING_AREAS);
+ }
+
+ /**
+ * Sets metering areas. See {@link #getMeteringAreas()} for
+ * documentation.
+ *
+ * @param meteringAreas the metering areas
+ * @see #getMeteringAreas()
+ */
+ public void setMeteringAreas(List<Area> meteringAreas) {
+ set(KEY_METERING_AREAS, meteringAreas);
+ }
+
// Splits a comma delimited string to an ArrayList of String.
// Return null if the passing string is null or the size is 0.
private ArrayList<String> split(String str) {
@@ -2617,5 +3050,41 @@ public class Camera {
if (rangeList.size() == 0) return null;
return rangeList;
}
+
+ // Splits a comma delimited string to an ArrayList of Area objects.
+ // Example string: "(-10,-10,0,0,300),(0,0,10,10,700)". Return null if
+ // the passing string is null or the size is 0 or (0,0,0,0,0).
+ private ArrayList<Area> splitArea(String str) {
+ if (str == null || str.charAt(0) != '('
+ || str.charAt(str.length() - 1) != ')') {
+ Log.e(TAG, "Invalid area string=" + str);
+ return null;
+ }
+
+ ArrayList<Area> result = new ArrayList<Area>();
+ int endIndex, fromIndex = 1;
+ int[] array = new int[5];
+ do {
+ endIndex = str.indexOf("),(", fromIndex);
+ if (endIndex == -1) endIndex = str.length() - 1;
+ splitInt(str.substring(fromIndex, endIndex), array);
+ Rect rect = new Rect(array[0], array[1], array[2], array[3]);
+ result.add(new Area(rect, array[4]));
+ fromIndex = endIndex + 3;
+ } while (endIndex != str.length() - 1);
+
+ if (result.size() == 0) return null;
+
+ if (result.size() == 1) {
+ Area area = result.get(0);
+ Rect rect = area.rect;
+ if (rect.left == 0 && rect.top == 0 && rect.right == 0
+ && rect.bottom == 0 && area.weight == 0) {
+ return null;
+ }
+ }
+
+ return result;
+ }
};
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 595c7d1..68fc101 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -66,7 +66,14 @@ public class Sensor {
/** A constant describing a pressure sensor type */
public static final int TYPE_PRESSURE = 6;
- /** A constant describing a temperature sensor type */
+ /**
+ * A constant describing a temperature sensor type
+ *
+ * @deprecated use
+ * {@link android.hardware.Sensor#TYPE_AMBIENT_TEMPERATURE
+ * Sensor.TYPE_AMBIENT_TEMPERATURE} instead.
+ */
+ @Deprecated
public static final int TYPE_TEMPERATURE = 7;
/**
@@ -104,6 +111,9 @@ public class Sensor {
*/
public static final int TYPE_RELATIVE_HUMIDITY = 12;
+ /** A constant describing an ambient temperature sensor type */
+ public static final int TYPE_AMBIENT_TEMPERATURE = 13;
+
/**
* A constant describing all sensor types.
*/
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 9b62a68..0411b5c 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -204,6 +204,12 @@ public class SensorEvent {
* values[0]: Ambient light level in SI lux units
* </ul>
*
+ * <h4>{@link android.hardware.Sensor#TYPE_PRESSURE Sensor.TYPE_PRESSURE}:</h4>
+ * <ul>
+ * <p>
+ * values[0]: Atmospheric pressure in hPa (millibar)
+ * </ul>
+ *
* <h4>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:
* </h4>
*
@@ -247,6 +253,23 @@ public class SensorEvent {
* <p>Elements of the rotation vector are unitless.
* The x,y, and z axis are defined in the same way as the acceleration
* sensor.</p>
+ * The reference coordinate system is defined as a direct orthonormal basis,
+ * where:
+ * </p>
+ *
+ * <ul>
+ * <li>X is defined as the vector product <b>Y.Z</b> (It is tangential to
+ * the ground at the device's current location and roughly points East).</li>
+ * <li>Y is tangential to the ground at the device's current location and
+ * points towards the magnetic North Pole.</li>
+ * <li>Z points towards the sky and is perpendicular to the ground.</li>
+ * </ul>
+ *
+ * <p>
+ * <center><img src="../../../images/axis_globe.png"
+ * alt="World coordinate-system diagram." border="0" /></center>
+ * </p>
+ *
* <ul>
* <p>
* values[0]: x*sin(&#952/2)
@@ -362,6 +385,14 @@ public class SensorEvent {
* dv = 216.7 *
* (rh / 100.0 * 6.112 * Math.exp(17.62 * t / (243.12 + t)) / (273.15 + t));
* </pre>
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_AMBIENT_TEMPERATURE Sensor.TYPE_AMBIENT_TEMPERATURE}:
+ * </h4>
+ *
+ * <ul>
+ * <p>
+ * values[0]: ambient (room) temperature in degree Celsius.
+ * </ul>
*
* @see SensorEvent
* @see GeomagneticField
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 60b37a1..5994c98 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -50,11 +50,20 @@ public class UsbManager {
* This is a sticky broadcast for clients that includes USB connected/disconnected state,
* <ul>
* <li> {@link #USB_CONNECTED} boolean indicating whether USB is connected or disconnected.
- * <li> {@link #USB_CONFIGURATION} a Bundle containing name/value pairs where the name
- * is the name of a USB function and the value is either {@link #USB_FUNCTION_ENABLED}
- * or {@link #USB_FUNCTION_DISABLED}. The possible function names include
- * {@link #USB_FUNCTION_MASS_STORAGE}, {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS},
- * {@link #USB_FUNCTION_MTP} and {@link #USB_FUNCTION_ACCESSORY}.
+ * <li> {@link #USB_CONFIGURATION} integer containing current USB configuration
+ * currently zero if not configured, one for configured.
+ * <li> {@link #USB_FUNCTION_MASS_STORAGE} boolean extra indicating whether the
+ * mass storage function is enabled
+ * <li> {@link #USB_FUNCTION_ADB} boolean extra indicating whether the
+ * adb function is enabled
+ * <li> {@link #USB_FUNCTION_RNDIS} boolean extra indicating whether the
+ * RNDIS ethernet function is enabled
+ * <li> {@link #USB_FUNCTION_MTP} boolean extra indicating whether the
+ * MTP function is enabled
+ * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
+ * PTP function is enabled
+ * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
+ * accessory function is enabled
* </ul>
*
* {@hide}
@@ -159,30 +168,20 @@ public class UsbManager {
public static final String USB_FUNCTION_MTP = "mtp";
/**
- * Name of the Accessory USB function.
+ * Name of the PTP USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
* {@hide}
*/
- public static final String USB_FUNCTION_ACCESSORY = "accessory";
-
- /**
- * Value indicating that a USB function is enabled.
- * Used in {@link #USB_CONFIGURATION} extras bundle for the
- * {@link #ACTION_USB_STATE} broadcast
- *
- * {@hide}
- */
- public static final String USB_FUNCTION_ENABLED = "enabled";
+ public static final String USB_FUNCTION_PTP = "ptp";
/**
- * Value indicating that a USB function is disabled.
- * Used in {@link #USB_CONFIGURATION} extras bundle for the
- * {@link #ACTION_USB_STATE} broadcast
+ * Name of the Accessory USB function.
+ * Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
* {@hide}
*/
- public static final String USB_FUNCTION_DISABLED = "disabled";
+ public static final String USB_FUNCTION_ACCESSORY = "accessory";
/**
* Name of extra for {@link #ACTION_USB_DEVICE_ATTACHED} and
diff --git a/core/java/android/inputmethodservice/ExtractButton.java b/core/java/android/inputmethodservice/ExtractButton.java
index f91cd4e..b6b7595 100644
--- a/core/java/android/inputmethodservice/ExtractButton.java
+++ b/core/java/android/inputmethodservice/ExtractButton.java
@@ -41,6 +41,6 @@ class ExtractButton extends Button {
* highlight when selected.
*/
@Override public boolean hasWindowFocus() {
- return this.isEnabled() ? true : false;
+ return isEnabled() && getVisibility() == VISIBLE ? true : false;
}
}
diff --git a/core/java/android/inputmethodservice/ExtractEditLayout.java b/core/java/android/inputmethodservice/ExtractEditLayout.java
new file mode 100644
index 0000000..eafff49
--- /dev/null
+++ b/core/java/android/inputmethodservice/ExtractEditLayout.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice;
+
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPopupHelper;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+/**
+ * ExtractEditLayout provides an ActionMode presentation for the
+ * limited screen real estate in extract mode.
+ *
+ * @hide
+ */
+public class ExtractEditLayout extends LinearLayout {
+ ExtractActionMode mActionMode;
+ Button mExtractActionButton;
+ Button mEditButton;
+
+ public ExtractEditLayout(Context context) {
+ super(context);
+ }
+
+ public ExtractEditLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(View sourceView, ActionMode.Callback cb) {
+ final ExtractActionMode mode = new ExtractActionMode(cb);
+ if (mode.dispatchOnCreate()) {
+ mode.invalidate();
+ mExtractActionButton.setVisibility(INVISIBLE);
+ mEditButton.setVisibility(VISIBLE);
+ mActionMode = mode;
+ return mode;
+ }
+ return null;
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ mExtractActionButton = (Button) findViewById(com.android.internal.R.id.inputExtractAction);
+ mEditButton = (Button) findViewById(com.android.internal.R.id.inputExtractEditButton);
+ mEditButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View clicked) {
+ if (mActionMode != null) {
+ new MenuPopupHelper(getContext(), mActionMode.mMenu, clicked).show();
+ }
+ }
+ });
+ }
+
+ private class ExtractActionMode extends ActionMode implements MenuBuilder.Callback {
+ private ActionMode.Callback mCallback;
+ MenuBuilder mMenu;
+
+ public ExtractActionMode(Callback cb) {
+ mMenu = new MenuBuilder(getContext());
+ mMenu.setCallback(this);
+ mCallback = cb;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ // Title will not be shown.
+ }
+
+ @Override
+ public void setTitle(int resId) {
+ // Title will nor be shown.
+ }
+
+ @Override
+ public void setSubtitle(CharSequence subtitle) {
+ // Subtitle will not be shown.
+ }
+
+ @Override
+ public void setSubtitle(int resId) {
+ // Subtitle will not be shown.
+ }
+
+ @Override
+ public void setCustomView(View view) {
+ // Custom view is not supported here.
+ }
+
+ @Override
+ public void invalidate() {
+ mMenu.stopDispatchingItemsChanged();
+ try {
+ mCallback.onPrepareActionMode(this, mMenu);
+ } finally {
+ mMenu.startDispatchingItemsChanged();
+ }
+ }
+
+ public boolean dispatchOnCreate() {
+ mMenu.stopDispatchingItemsChanged();
+ try {
+ return mCallback.onCreateActionMode(this, mMenu);
+ } finally {
+ mMenu.startDispatchingItemsChanged();
+ }
+ }
+
+ @Override
+ public void finish() {
+ if (mActionMode != this) {
+ // Not the active action mode - no-op
+ return;
+ }
+
+ mCallback.onDestroyActionMode(this);
+ mCallback = null;
+
+ mExtractActionButton.setVisibility(VISIBLE);
+ mEditButton.setVisibility(INVISIBLE);
+
+ mActionMode = null;
+ }
+
+ @Override
+ public Menu getMenu() {
+ return mMenu;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getSubtitle() {
+ return null;
+ }
+
+ @Override
+ public View getCustomView() {
+ return null;
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return new MenuInflater(getContext());
+ }
+
+ @Override
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mCallback.onActionItemClicked(this, item);
+ }
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+
+ }
+}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index ab5c78a..dfc70ef 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -142,7 +142,8 @@ public class KeyboardView extends View implements View.OnClickListener {
private int mPreviewTextSizeLarge;
private int mPreviewOffset;
private int mPreviewHeight;
- private int[] mOffsetInWindow;
+ // Working variable
+ private final int[] mCoordinates = new int[2];
private PopupWindow mPopupKeyboard;
private View mMiniKeyboardContainer;
@@ -152,7 +153,6 @@ public class KeyboardView extends View implements View.OnClickListener {
private int mMiniKeyboardOffsetX;
private int mMiniKeyboardOffsetY;
private Map<Key,View> mMiniKeyboardCache;
- private int[] mWindowOffset;
private Key[] mKeys;
/** Listener for {@link OnKeyboardActionListener}. */
@@ -905,23 +905,19 @@ public class KeyboardView extends View implements View.OnClickListener {
mPopupPreviewY = - mPreviewText.getMeasuredHeight();
}
mHandler.removeMessages(MSG_REMOVE_PREVIEW);
- if (mOffsetInWindow == null) {
- mOffsetInWindow = new int[2];
- getLocationInWindow(mOffsetInWindow);
- mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
- mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
- int[] mWindowLocation = new int[2];
- getLocationOnScreen(mWindowLocation);
- mWindowY = mWindowLocation[1];
- }
+ getLocationInWindow(mCoordinates);
+ mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
+ mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
+
// Set the preview background state
mPreviewText.getBackground().setState(
key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- mPopupPreviewX += mOffsetInWindow[0];
- mPopupPreviewY += mOffsetInWindow[1];
+ mPopupPreviewX += mCoordinates[0];
+ mPopupPreviewY += mCoordinates[1];
// If the popup cannot be shown above the key, put it on the side
- if (mPopupPreviewY + mWindowY < 0) {
+ getLocationOnScreen(mCoordinates);
+ if (mPopupPreviewY + mCoordinates[1] < 0) {
// If the key you're pressing is on the left side of the keyboard, show the popup on
// the right, offset by enough to see at least one key to the left/right.
if (key.x + key.width <= getWidth() / 2) {
@@ -1057,16 +1053,13 @@ public class KeyboardView extends View implements View.OnClickListener {
mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
com.android.internal.R.id.keyboardView);
}
- if (mWindowOffset == null) {
- mWindowOffset = new int[2];
- getLocationInWindow(mWindowOffset);
- }
+ getLocationInWindow(mCoordinates);
mPopupX = popupKey.x + mPaddingLeft;
mPopupY = popupKey.y + mPaddingTop;
mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
- final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
- final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
+ final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
+ final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
mMiniKeyboard.setShifted(isShifted());
mPopupKeyboard.setContentView(mMiniKeyboardContainer);
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 343242e..7159260 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -73,8 +73,17 @@ class SoftInputWindow extends Dialog {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getWindow().getDecorView().getHitRect(mBounds);
- final MotionEvent event = clipMotionEvent(ev, mBounds);
- return super.dispatchTouchEvent(event);
+
+ if (ev.isWithinBoundsNoHistory(mBounds.left, mBounds.top,
+ mBounds.right - 1, mBounds.bottom - 1)) {
+ return super.dispatchTouchEvent(ev);
+ } else {
+ MotionEvent temp = ev.clampNoHistory(mBounds.left, mBounds.top,
+ mBounds.right - 1, mBounds.bottom - 1);
+ boolean handled = super.dispatchTouchEvent(temp);
+ temp.recycle();
+ return handled;
+ }
}
/**
@@ -163,48 +172,4 @@ class SoftInputWindow extends Dialog {
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_DIM_BEHIND);
}
-
- private static MotionEvent clipMotionEvent(MotionEvent me, Rect bounds) {
- final int pointerCount = me.getPointerCount();
- boolean shouldClip = false;
- for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
- final int x = (int)me.getX(pointerIndex);
- final int y = (int)me.getY(pointerIndex);
- if (!bounds.contains(x, y)) {
- shouldClip = true;
- break;
- }
- }
- if (!shouldClip)
- return me;
-
- if (pointerCount == 1) {
- final int x = (int)me.getX();
- final int y = (int)me.getY();
- me.setLocation(
- Math.max(bounds.left, Math.min(x, bounds.right - 1)),
- Math.max(bounds.top, Math.min(y, bounds.bottom - 1)));
- return me;
- }
-
- final int[] pointerIds = new int[pointerCount];
- final MotionEvent.PointerCoords[] pointerCoords =
- new MotionEvent.PointerCoords[pointerCount];
- for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
- pointerIds[pointerIndex] = me.getPointerId(pointerIndex);
- final MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
- me.getPointerCoords(pointerIndex, coords);
- pointerCoords[pointerIndex] = coords;
- final int x = (int)coords.x;
- final int y = (int)coords.y;
- if (!bounds.contains(x, y)) {
- coords.x = Math.max(bounds.left, Math.min(x, bounds.right - 1));
- coords.y = Math.max(bounds.top, Math.min(y, bounds.bottom - 1));
- }
- }
- return MotionEvent.obtain(
- me.getDownTime(), me.getEventTime(), me.getAction(), pointerCount, pointerIds,
- pointerCoords, me.getMetaState(), me.getXPrecision(), me.getYPrecision(),
- me.getDeviceId(), me.getEdgeFlags(), me.getSource(), me.getFlags());
- }
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index eaf9191..3025462 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -19,10 +19,11 @@ package android.net;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.os.Binder;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import java.net.InetAddress;
-import java.net.UnknownHostException;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -40,8 +41,9 @@ import java.net.UnknownHostException;
* state of the available networks</li>
* </ol>
*/
-public class ConnectivityManager
-{
+public class ConnectivityManager {
+ private static final String TAG = "ConnectivityManager";
+
/**
* A change in network connectivity has occurred. A connection has either
* been established or lost. The NetworkInfo for the affected network is
@@ -109,7 +111,7 @@ public class ConnectivityManager
* The lookup key for an int that provides information about
* our connection to the internet at large. 0 indicates no connection,
* 100 indicates a great connection. Retrieve it with
- * {@link android.content.Intent@getIntExtra(String)}.
+ * {@link android.content.Intent#getIntExtra(String, int)}.
* {@hide}
*/
public static final String EXTRA_INET_CONDITION = "inetCondition";
@@ -120,13 +122,12 @@ public class ConnectivityManager
* <p>
* If an application uses the network in the background, it should listen
* for this broadcast and stop using the background data if the value is
- * false.
+ * {@code false}.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
-
/**
* Broadcast Action: The network connection may not be good
* uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
@@ -216,7 +217,9 @@ public class ConnectivityManager
*/
public static final int TYPE_BLUETOOTH = 7;
- /** {@hide} */
+ /**
+ * Dummy data connection. This should not be used on shipping devices.
+ */
public static final int TYPE_DUMMY = 8;
/**
@@ -224,6 +227,7 @@ public class ConnectivityManager
* will use this connection by default.
*/
public static final int TYPE_ETHERNET = 9;
+
/**
* Over the air Adminstration.
* {@hide}
@@ -250,12 +254,63 @@ public class ConnectivityManager
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
- private IConnectivityManager mService;
+ private final IConnectivityManager mService;
- static public boolean isNetworkTypeValid(int networkType) {
+ public static boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
}
+ /** {@hide} */
+ public static String getNetworkTypeName(int type) {
+ switch (type) {
+ case TYPE_MOBILE:
+ return "MOBILE";
+ case TYPE_WIFI:
+ return "WIFI";
+ case TYPE_MOBILE_MMS:
+ return "MOBILE_MMS";
+ case TYPE_MOBILE_SUPL:
+ return "MOBILE_SUPL";
+ case TYPE_MOBILE_DUN:
+ return "MOBILE_DUN";
+ case TYPE_MOBILE_HIPRI:
+ return "MOBILE_HIPRI";
+ case TYPE_WIMAX:
+ return "WIMAX";
+ case TYPE_BLUETOOTH:
+ return "BLUETOOTH";
+ case TYPE_DUMMY:
+ return "DUMMY";
+ case TYPE_ETHERNET:
+ return "ETHERNET";
+ case TYPE_MOBILE_FOTA:
+ return "MOBILE_FOTA";
+ case TYPE_MOBILE_IMS:
+ return "MOBILE_IMS";
+ case TYPE_MOBILE_CBS:
+ return "MOBILE_CBS";
+ default:
+ return Integer.toString(type);
+ }
+ }
+
+ /** {@hide} */
+ public static boolean isNetworkTypeMobile(int networkType) {
+ switch (networkType) {
+ case TYPE_MOBILE:
+ case TYPE_MOBILE_MMS:
+ case TYPE_MOBILE_SUPL:
+ case TYPE_MOBILE_DUN:
+ case TYPE_MOBILE_HIPRI:
+ case TYPE_MOBILE_FOTA:
+ case TYPE_MOBILE_IMS:
+ case TYPE_MOBILE_CBS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public void setNetworkPreference(int preference) {
try {
mService.setNetworkPreference(preference);
@@ -279,6 +334,15 @@ public class ConnectivityManager
}
}
+ /** {@hide} */
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ try {
+ return mService.getActiveNetworkInfoForUid(uid);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -295,7 +359,7 @@ public class ConnectivityManager
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -304,7 +368,7 @@ public class ConnectivityManager
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkProperties(networkType);
@@ -474,19 +538,11 @@ public class ConnectivityManager
}
/**
- * Don't allow use of default constructor.
- */
- @SuppressWarnings({"UnusedDeclaration"})
- private ConnectivityManager() {
- }
-
- /**
* {@hide}
*/
public ConnectivityManager(IConnectivityManager service) {
if (service == null) {
- throw new IllegalArgumentException(
- "ConnectivityManager() cannot be constructed with null service");
+ throw new IllegalArgumentException("missing IConnectivityManager");
}
mService = service;
}
@@ -702,4 +758,43 @@ public class ConnectivityManager
} catch (RemoteException e) {
}
}
+
+ /**
+ * Protect a socket from routing changes. This method is limited to VPN
+ * applications, and it is always hidden to avoid direct use.
+ * @hide
+ */
+ public void protectVpn(ParcelFileDescriptor socket) {
+ try {
+ mService.protectVpn(socket);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Prepare for a VPN application. This method is limited to VpnDialogs,
+ * and it is always hidden to avoid direct use.
+ * @hide
+ */
+ public String prepareVpn(String packageName) {
+ try {
+ return mService.prepareVpn(packageName);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Configure a TUN interface and return its file descriptor. Parameters
+ * are encoded and opaque to this class. This method is limited to VPN
+ * applications, and it is always hidden to avoid direct use.
+ * @hide
+ */
+ public ParcelFileDescriptor establishVpn(Bundle config) {
+ try {
+ return mService.establishVpn(config);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 8be492c..7f3775d 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -18,8 +18,11 @@ package android.net;
import android.net.LinkProperties;
import android.net.NetworkInfo;
+import android.net.NetworkState;
import android.net.ProxyProperties;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
/**
* Interface that answers queries about, and allows changing, the
@@ -33,15 +36,15 @@ interface IConnectivityManager
int getNetworkPreference();
NetworkInfo getActiveNetworkInfo();
-
+ NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
-
NetworkInfo[] getAllNetworkInfo();
LinkProperties getActiveLinkProperties();
-
LinkProperties getLinkProperties(int networkType);
+ NetworkState[] getAllNetworkState();
+
boolean setRadios(boolean onOff);
boolean setRadio(int networkType, boolean turnOn);
@@ -94,4 +97,10 @@ interface IConnectivityManager
ProxyProperties getProxy();
void setDataDependency(int networkType, boolean met);
+
+ void protectVpn(in ParcelFileDescriptor socket);
+
+ String prepareVpn(String packageName);
+
+ ParcelFileDescriptor establishVpn(in Bundle config);
}
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
new file mode 100644
index 0000000..9230151
--- /dev/null
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** {@hide} */
+oneway interface INetworkPolicyListener {
+
+ void onRulesChanged(int uid, int uidRules);
+
+}
diff --git a/core/java/android/os/INetStatService.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index a8f3de0..c1f3530 100644
--- a/core/java/android/os/INetStatService.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,22 +14,25 @@
* limitations under the License.
*/
-package android.os;
+package android.net;
+
+import android.net.INetworkPolicyListener;
/**
- * Retrieves packet and byte counts for the phone data interface,
- * and for all interfaces.
- * Used for the data activity icon and the phone status in Settings.
+ * Interface that creates and modifies network policy rules.
*
* {@hide}
*/
-interface INetStatService {
- long getMobileTxPackets();
- long getMobileRxPackets();
- long getMobileTxBytes();
- long getMobileRxBytes();
- long getTotalTxPackets();
- long getTotalRxPackets();
- long getTotalTxBytes();
- long getTotalRxBytes();
+interface INetworkPolicyManager {
+
+ void setUidPolicy(int uid, int policy);
+ int getUidPolicy(int uid);
+
+ boolean isUidForeground(int uid);
+
+ void registerListener(INetworkPolicyListener listener);
+ void unregisterListener(INetworkPolicyListener listener);
+
+ // TODO: build API to surface stats details for settings UI
+
}
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
new file mode 100644
index 0000000..d05c9d3
--- /dev/null
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+
+/** {@hide} */
+interface INetworkStatsService {
+
+ /** Return historical stats for traffic that matches template. */
+ NetworkStatsHistory getHistoryForNetwork(int networkTemplate);
+ /** Return historical stats for specific UID traffic that matches template. */
+ NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate);
+
+ /** Return usage summary per UID for traffic that matches template. */
+ NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate);
+
+}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 5f5e11c..537750a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -74,7 +74,9 @@ public class NetworkInfo implements Parcelable {
/** IP traffic not available. */
DISCONNECTED,
/** Attempt to connect failed. */
- FAILED
+ FAILED,
+ /** Access to this network is blocked. */
+ BLOCKED
}
/**
@@ -96,6 +98,7 @@ public class NetworkInfo implements Parcelable {
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+ stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
}
private int mNetworkType;
@@ -138,6 +141,23 @@ public class NetworkInfo implements Parcelable {
mIsRoaming = false;
}
+ /** {@hide} */
+ public NetworkInfo(NetworkInfo source) {
+ if (source != null) {
+ mNetworkType = source.mNetworkType;
+ mSubtype = source.mSubtype;
+ mTypeName = source.mTypeName;
+ mSubtypeName = source.mSubtypeName;
+ mState = source.mState;
+ mDetailedState = source.mDetailedState;
+ mReason = source.mReason;
+ mExtraInfo = source.mExtraInfo;
+ mIsFailover = source.mIsFailover;
+ mIsRoaming = source.mIsRoaming;
+ mIsAvailable = source.mIsAvailable;
+ }
+ }
+
/**
* Reports the type of network (currently mobile or Wi-Fi) to which the
* info in this object pertains.
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
new file mode 100644
index 0000000..dd7c1b0
--- /dev/null
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.io.PrintWriter;
+
+/**
+ * Manager for creating and modifying network policy rules.
+ *
+ * {@hide}
+ */
+public class NetworkPolicyManager {
+
+ /** No specific network policy, use system default. */
+ public static final int POLICY_NONE = 0x0;
+ /** Reject network usage on paid networks when application in background. */
+ public static final int POLICY_REJECT_PAID_BACKGROUND = 0x1;
+
+ /** All network traffic should be allowed. */
+ public static final int RULE_ALLOW_ALL = 0x0;
+ /** Reject traffic on paid networks. */
+ public static final int RULE_REJECT_PAID = 0x1;
+
+ private INetworkPolicyManager mService;
+
+ public NetworkPolicyManager(INetworkPolicyManager service) {
+ if (service == null) {
+ throw new IllegalArgumentException("missing INetworkPolicyManager");
+ }
+ mService = service;
+ }
+
+ public static NetworkPolicyManager getSystemService(Context context) {
+ return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE);
+ }
+
+ /**
+ * Set policy flags for specific UID.
+ *
+ * @param policy {@link #POLICY_NONE} or combination of flags like
+ * {@link #POLICY_REJECT_PAID_BACKGROUND}.
+ */
+ public void setUidPolicy(int uid, int policy) {
+ try {
+ mService.setUidPolicy(uid, policy);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public int getUidPolicy(int uid) {
+ try {
+ return mService.getUidPolicy(uid);
+ } catch (RemoteException e) {
+ return POLICY_NONE;
+ }
+ }
+
+ /** {@hide} */
+ public static void dumpPolicy(PrintWriter fout, int policy) {
+ fout.write("[");
+ if ((policy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+ fout.write("REJECT_PAID_BACKGROUND");
+ }
+ fout.write("]");
+ }
+
+ /** {@hide} */
+ public static void dumpRules(PrintWriter fout, int rules) {
+ fout.write("[");
+ if ((rules & RULE_REJECT_PAID) != 0) {
+ fout.write("REJECT_PAID");
+ }
+ fout.write("]");
+ }
+
+}
diff --git a/core/java/android/net/NetworkState.aidl b/core/java/android/net/NetworkState.aidl
new file mode 100644
index 0000000..c0b6cdc
--- /dev/null
+++ b/core/java/android/net/NetworkState.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkState;
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
new file mode 100644
index 0000000..749039a
--- /dev/null
+++ b/core/java/android/net/NetworkState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Snapshot of network state.
+ *
+ * @hide
+ */
+public class NetworkState implements Parcelable {
+
+ public final NetworkInfo networkInfo;
+ public final LinkProperties linkProperties;
+ public final LinkCapabilities linkCapabilities;
+
+ public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+ LinkCapabilities linkCapabilities) {
+ this.networkInfo = networkInfo;
+ this.linkProperties = linkProperties;
+ this.linkCapabilities = linkCapabilities;
+ }
+
+ public NetworkState(Parcel in) {
+ networkInfo = in.readParcelable(null);
+ linkProperties = in.readParcelable(null);
+ linkCapabilities = in.readParcelable(null);
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(networkInfo, flags);
+ out.writeParcelable(linkProperties, flags);
+ out.writeParcelable(linkCapabilities, flags);
+ }
+
+ public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
+ public NetworkState createFromParcel(Parcel in) {
+ return new NetworkState(in);
+ }
+
+ public NetworkState[] newArray(int size) {
+ return new NetworkState[size];
+ }
+ };
+
+}
diff --git a/core/java/android/net/NetworkStats.aidl b/core/java/android/net/NetworkStats.aidl
new file mode 100644
index 0000000..d06ca65
--- /dev/null
+++ b/core/java/android/net/NetworkStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkStats;
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
new file mode 100644
index 0000000..6354e9a
--- /dev/null
+++ b/core/java/android/net/NetworkStats.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.SparseBooleanArray;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+import java.util.HashSet;
+
+/**
+ * Collection of active network statistics. Can contain summary details across
+ * all interfaces, or details with per-UID granularity. Internally stores data
+ * as a large table, closely matching {@code /proc/} data format. This structure
+ * optimizes for rapid in-memory comparison, but consider using
+ * {@link NetworkStatsHistory} when persisting.
+ *
+ * @hide
+ */
+public class NetworkStats implements Parcelable {
+ /** {@link #iface} value when interface details unavailable. */
+ public static final String IFACE_ALL = null;
+ /** {@link #uid} value when UID details unavailable. */
+ public static final int UID_ALL = -1;
+
+ // NOTE: data should only be accounted for once in this structure; if data
+ // is broken out, the summarized version should not be included.
+
+ /**
+ * {@link SystemClock#elapsedRealtime()} timestamp when this data was
+ * generated.
+ */
+ public final long elapsedRealtime;
+ public final String[] iface;
+ public final int[] uid;
+ public final long[] rx;
+ public final long[] tx;
+
+ // TODO: add fg/bg stats once reported by kernel
+
+ private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) {
+ this.elapsedRealtime = elapsedRealtime;
+ this.iface = iface;
+ this.uid = uid;
+ this.rx = rx;
+ this.tx = tx;
+ }
+
+ public NetworkStats(Parcel parcel) {
+ elapsedRealtime = parcel.readLong();
+ iface = parcel.createStringArray();
+ uid = parcel.createIntArray();
+ rx = parcel.createLongArray();
+ tx = parcel.createLongArray();
+ }
+
+ public static class Builder {
+ private long mElapsedRealtime;
+ private final String[] mIface;
+ private final int[] mUid;
+ private final long[] mRx;
+ private final long[] mTx;
+
+ private int mIndex = 0;
+
+ public Builder(long elapsedRealtime, int size) {
+ mElapsedRealtime = elapsedRealtime;
+ mIface = new String[size];
+ mUid = new int[size];
+ mRx = new long[size];
+ mTx = new long[size];
+ }
+
+ public Builder addEntry(String iface, int uid, long rx, long tx) {
+ mIface[mIndex] = iface;
+ mUid[mIndex] = uid;
+ mRx[mIndex] = rx;
+ mTx[mIndex] = tx;
+ mIndex++;
+ return this;
+ }
+
+ public NetworkStats build() {
+ if (mIndex != mIface.length) {
+ throw new IllegalArgumentException("unexpected number of entries");
+ }
+ return new NetworkStats(mElapsedRealtime, mIface, mUid, mRx, mTx);
+ }
+ }
+
+ public int length() {
+ // length is identical for all fields
+ return iface.length;
+ }
+
+ /**
+ * Find first stats index that matches the requested parameters.
+ */
+ public int findIndex(String iface, int uid) {
+ final int length = length();
+ for (int i = 0; i < length; i++) {
+ if (equal(iface, this.iface[i]) && uid == this.uid[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Return list of unique interfaces known by this data structure.
+ */
+ public String[] getUniqueIfaces() {
+ final HashSet<String> ifaces = new HashSet<String>();
+ for (String iface : this.iface) {
+ if (iface != IFACE_ALL) {
+ ifaces.add(iface);
+ }
+ }
+ return ifaces.toArray(new String[ifaces.size()]);
+ }
+
+ /**
+ * Return list of unique UIDs known by this data structure.
+ */
+ public int[] getUniqueUids() {
+ final SparseBooleanArray uids = new SparseBooleanArray();
+ for (int uid : this.uid) {
+ uids.put(uid, true);
+ }
+
+ final int size = uids.size();
+ final int[] result = new int[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = uids.keyAt(i);
+ }
+ return result;
+ }
+
+ /**
+ * Subtract the given {@link NetworkStats}, effectively leaving the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ *
+ * @throws IllegalArgumentException when given {@link NetworkStats} is
+ * non-monotonic.
+ */
+ public NetworkStats subtract(NetworkStats value) {
+ return subtract(value, true, false);
+ }
+
+ /**
+ * Subtract the given {@link NetworkStats}, effectively leaving the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ * <p>
+ * Instead of throwing when counters are non-monotonic, this variant clamps
+ * results to never be negative.
+ */
+ public NetworkStats subtractClamped(NetworkStats value) {
+ return subtract(value, false, true);
+ }
+
+ /**
+ * Subtract the given {@link NetworkStats}, effectively leaving the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ *
+ * @param enforceMonotonic Validate that incoming value is strictly
+ * monotonic compared to this object.
+ * @param clampNegative Instead of throwing like {@code enforceMonotonic},
+ * clamp resulting counters at 0 to prevent negative values.
+ */
+ private NetworkStats subtract(
+ NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
+ final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
+ if (enforceMonotonic && deltaRealtime < 0) {
+ throw new IllegalArgumentException("found non-monotonic realtime");
+ }
+
+ // result will have our rows, and elapsed time between snapshots
+ final int length = length();
+ final NetworkStats.Builder result = new NetworkStats.Builder(deltaRealtime, length);
+ for (int i = 0; i < length; i++) {
+ final String iface = this.iface[i];
+ final int uid = this.uid[i];
+
+ // find remote row that matches, and subtract
+ final int j = value.findIndex(iface, uid);
+ if (j == -1) {
+ // newly appearing row, return entire value
+ result.addEntry(iface, uid, this.rx[i], this.tx[i]);
+ } else {
+ // existing row, subtract remote value
+ long rx = this.rx[i] - value.rx[j];
+ long tx = this.tx[i] - value.tx[j];
+ if (enforceMonotonic && (rx < 0 || tx < 0)) {
+ throw new IllegalArgumentException("found non-monotonic values");
+ }
+ if (clampNegative) {
+ rx = Math.max(0, rx);
+ tx = Math.max(0, tx);
+ }
+ result.addEntry(iface, uid, rx, tx);
+ }
+ }
+
+ return result.build();
+ }
+
+ private static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
+ for (int i = 0; i < iface.length; i++) {
+ pw.print(prefix);
+ pw.print(" iface="); pw.print(iface[i]);
+ pw.print(" uid="); pw.print(uid[i]);
+ pw.print(" rx="); pw.print(rx[i]);
+ pw.print(" tx="); pw.println(tx[i]);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump("", new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(elapsedRealtime);
+ dest.writeStringArray(iface);
+ dest.writeIntArray(uid);
+ dest.writeLongArray(rx);
+ dest.writeLongArray(tx);
+ }
+
+ public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
+ public NetworkStats createFromParcel(Parcel in) {
+ return new NetworkStats(in);
+ }
+
+ public NetworkStats[] newArray(int size) {
+ return new NetworkStats[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkStatsHistory.aidl b/core/java/android/net/NetworkStatsHistory.aidl
new file mode 100644
index 0000000..8b9069f
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkStatsHistory;
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
new file mode 100644
index 0000000..a697e96
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.CharArrayWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.ProtocolException;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Collection of historical network statistics, recorded into equally-sized
+ * "buckets" in time. Internally it stores data in {@code long} series for more
+ * efficient persistence.
+ * <p>
+ * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
+ * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
+ * sorted at all times.
+ *
+ * @hide
+ */
+public class NetworkStatsHistory implements Parcelable {
+ private static final int VERSION_CURRENT = 1;
+
+ // TODO: teach about zigzag encoding to use less disk space
+ // TODO: teach how to convert between bucket sizes
+
+ public final long bucketDuration;
+
+ public int bucketCount;
+ public long[] bucketStart;
+ public long[] rx;
+ public long[] tx;
+
+ public NetworkStatsHistory(long bucketDuration) {
+ this.bucketDuration = bucketDuration;
+ bucketStart = new long[0];
+ rx = new long[0];
+ tx = new long[0];
+ bucketCount = bucketStart.length;
+ }
+
+ public NetworkStatsHistory(Parcel in) {
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ rx = in.createLongArray();
+ tx = in.createLongArray();
+ bucketCount = bucketStart.length;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, rx, bucketCount);
+ writeLongArray(out, tx, bucketCount);
+ }
+
+ public NetworkStatsHistory(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_CURRENT: {
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ rx = readLongArray(in);
+ tx = readLongArray(in);
+ bucketCount = bucketStart.length;
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ }
+
+ public void writeToStream(DataOutputStream out) throws IOException {
+ out.writeInt(VERSION_CURRENT);
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, rx, bucketCount);
+ writeLongArray(out, tx, bucketCount);
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Record that data traffic occurred in the given time range. Will
+ * distribute across internal buckets, creating new buckets as needed.
+ */
+ public void recordData(long start, long end, long rx, long tx) {
+ // create any buckets needed by this range
+ ensureBuckets(start, end);
+
+ // distribute data usage into buckets
+ final long duration = end - start;
+ for (int i = bucketCount - 1; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // bucket is older than record; we're finished
+ if (curEnd < start) break;
+ // bucket is newer than record; keep looking
+ if (curStart > end) continue;
+
+ final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
+ if (overlap > 0) {
+ this.rx[i] += rx * overlap / duration;
+ this.tx[i] += tx * overlap / duration;
+ }
+ }
+ }
+
+ /**
+ * Record an entire {@link NetworkStatsHistory} into this history. Usually
+ * for combining together stats for external reporting.
+ */
+ public void recordEntireHistory(NetworkStatsHistory input) {
+ for (int i = 0; i < input.bucketCount; i++) {
+ final long start = input.bucketStart[i];
+ final long end = start + input.bucketDuration;
+ recordData(start, end, input.rx[i], input.tx[i]);
+ }
+ }
+
+ /**
+ * Ensure that buckets exist for given time range, creating as needed.
+ */
+ private void ensureBuckets(long start, long end) {
+ // normalize incoming range to bucket boundaries
+ start -= start % bucketDuration;
+ end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
+
+ for (long now = start; now < end; now += bucketDuration) {
+ // try finding existing bucket
+ final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
+ if (index < 0) {
+ // bucket missing, create and insert
+ insertBucket(~index, now);
+ }
+ }
+ }
+
+ /**
+ * Insert new bucket at requested index and starting time.
+ */
+ private void insertBucket(int index, long start) {
+ // create more buckets when needed
+ if (bucketCount + 1 > bucketStart.length) {
+ final int newLength = bucketStart.length + 10;
+ bucketStart = Arrays.copyOf(bucketStart, newLength);
+ rx = Arrays.copyOf(rx, newLength);
+ tx = Arrays.copyOf(tx, newLength);
+ }
+
+ // create gap when inserting bucket in middle
+ if (index < bucketCount) {
+ final int dstPos = index + 1;
+ final int length = bucketCount - index;
+
+ System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
+ System.arraycopy(rx, index, rx, dstPos, length);
+ System.arraycopy(tx, index, tx, dstPos, length);
+ }
+
+ bucketStart[index] = start;
+ rx[index] = 0;
+ tx[index] = 0;
+ bucketCount++;
+ }
+
+ /**
+ * Remove buckets older than requested cutoff.
+ */
+ public void removeBucketsBefore(long cutoff) {
+ int i;
+ for (i = 0; i < bucketCount; i++) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // cutoff happens before or during this bucket; everything before
+ // this bucket should be removed.
+ if (curEnd > cutoff) break;
+ }
+
+ if (i > 0) {
+ final int length = bucketStart.length;
+ bucketStart = Arrays.copyOfRange(bucketStart, i, length);
+ rx = Arrays.copyOfRange(rx, i, length);
+ tx = Arrays.copyOfRange(tx, i, length);
+ bucketCount -= i;
+ }
+ }
+
+ /**
+ * Return interpolated data usage across the requested range. Interpolates
+ * across buckets, so values may be rounded slightly.
+ */
+ public long[] getTotalData(long start, long end, long[] outTotal) {
+ long rx = 0;
+ long tx = 0;
+
+ for (int i = bucketCount - 1; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // bucket is older than record; we're finished
+ if (curEnd < start) break;
+ // bucket is newer than record; keep looking
+ if (curStart > end) continue;
+
+ final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
+ if (overlap > 0) {
+ rx += this.rx[i] * overlap / bucketDuration;
+ tx += this.tx[i] * overlap / bucketDuration;
+ }
+ }
+
+ if (outTotal == null || outTotal.length != 2) {
+ outTotal = new long[2];
+ }
+ outTotal[0] = rx;
+ outTotal[1] = tx;
+ return outTotal;
+ }
+
+ /**
+ * @deprecated only for temporary testing
+ */
+ @Deprecated
+ public void generateRandom(long start, long end, long rx, long tx) {
+ ensureBuckets(start, end);
+
+ final Random r = new Random();
+ while (rx > 1024 && tx > 1024) {
+ final long curStart = randomLong(r, start, end);
+ final long curEnd = randomLong(r, curStart, end);
+ final long curRx = randomLong(r, 0, rx);
+ final long curTx = randomLong(r, 0, tx);
+
+ recordData(curStart, curEnd, curRx, curTx);
+
+ rx -= curRx;
+ tx -= curTx;
+ }
+ }
+
+ private static long randomLong(Random r, long start, long end) {
+ return (long) (start + (r.nextFloat() * (end - start)));
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
+ for (int i = 0; i < bucketCount; i++) {
+ pw.print(prefix);
+ pw.print(" bucketStart="); pw.print(bucketStart[i]);
+ pw.print(" rx="); pw.print(rx[i]);
+ pw.print(" tx="); pw.println(tx[i]);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump("", new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
+ public NetworkStatsHistory createFromParcel(Parcel in) {
+ return new NetworkStatsHistory(in);
+ }
+
+ public NetworkStatsHistory[] newArray(int size) {
+ return new NetworkStatsHistory[size];
+ }
+ };
+
+ private static long[] readLongArray(DataInputStream in) throws IOException {
+ final int size = in.readInt();
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+
+ private static long[] readLongArray(Parcel in) {
+ final int size = in.readInt();
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ private static void writeLongArray(Parcel out, long[] values, int size) {
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+
+}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index f8f8a29..8e50cd5 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -17,30 +17,24 @@
package android.net;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
-import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
@@ -93,6 +87,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
private SSLSocketFactory mInsecureFactory = null;
private SSLSocketFactory mSecureFactory = null;
+ private TrustManager[] mTrustManagers = null;
+ private KeyManager[] mKeyManagers = null;
private final int mHandshakeTimeoutMillis;
private final SSLClientSessionCache mSessionCache;
@@ -128,7 +124,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
- * @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
* @return a new SSLSocketFactory with the specified parameters
*/
public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) {
@@ -144,7 +140,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
- * @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
* @return an insecure SSLSocketFactory with the specified parameters
*/
public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) {
@@ -157,12 +153,11 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
- * @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
* @return a new SocketFactory with the specified parameters
*/
public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
- int handshakeTimeoutMillis,
- SSLSessionCache cache) {
+ int handshakeTimeoutMillis, SSLSessionCache cache) {
return new org.apache.http.conn.ssl.SSLSocketFactory(
new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true));
}
@@ -205,10 +200,11 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
}
}
- private SSLSocketFactory makeSocketFactory(TrustManager[] trustManagers) {
+ private SSLSocketFactory makeSocketFactory(
+ KeyManager[] keyManagers, TrustManager[] trustManagers) {
try {
OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
- sslContext.engineInit(null, trustManagers, null);
+ sslContext.engineInit(keyManagers, trustManagers, null);
sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
return sslContext.engineGetSocketFactory();
} catch (KeyManagementException e) {
@@ -231,18 +227,44 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
} else {
Log.w(TAG, "Bypassing SSL security checks at caller's request");
}
- mInsecureFactory = makeSocketFactory(INSECURE_TRUST_MANAGER);
+ mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER);
}
return mInsecureFactory;
} else {
if (mSecureFactory == null) {
- mSecureFactory = makeSocketFactory(null);
+ mSecureFactory = makeSocketFactory(mKeyManagers, mTrustManagers);
}
return mSecureFactory;
}
}
/**
+ * Sets the {@link TrustManager}s to be used for connections made by this factory.
+ * @hide
+ */
+ public void setTrustManagers(TrustManager[] trustManager) {
+ mTrustManagers = trustManager;
+
+ // Clear out all cached secure factories since configurations have changed.
+ mSecureFactory = null;
+ // Note - insecure factories only ever use the INSECURE_TRUST_MANAGER so they need not
+ // be cleared out here.
+ }
+
+ /**
+ * Sets the {@link KeyManager}s to be used for connections made by this factory.
+ * @hide
+ */
+ public void setKeyManagers(KeyManager[] keyManagers) {
+ mKeyManagers = keyManagers;
+
+ // Clear out any existing cached factories since configurations have changed.
+ mSecureFactory = null;
+ mInsecureFactory = null;
+ }
+
+
+ /**
* {@inheritDoc}
*
* <p>This method verifies the peer's certificate hostname after connecting
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index 3e21e2d..316440f 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -17,7 +17,6 @@
package android.net;
import android.os.SystemClock;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
@@ -112,8 +111,8 @@ public class SntpClient
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
- // if (Config.LOGD) Log.d(TAG, "round trip: " + roundTripTime + " ms");
- // if (Config.LOGD) Log.d(TAG, "clock offset: " + clockOffset + " ms");
+ // if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");
+ // if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");
// save our results - use the times on this side of the network latency
// (response rather than request time)
@@ -121,7 +120,7 @@ public class SntpClient
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
- if (Config.LOGD) Log.d(TAG, "request time failed: " + e);
+ if (false) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
if (socket != null) {
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index eca06c5..8a688d5 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -16,11 +16,16 @@
package android.net;
-import android.util.Log;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
-import java.io.File;
-import java.io.RandomAccessFile;
-import java.io.IOException;
+import dalvik.system.BlockGuard;
+
+import java.net.Socket;
+import java.net.SocketException;
/**
* Class that provides network traffic statistics. These statistics include
@@ -36,6 +41,149 @@ public class TrafficStats {
*/
public final static int UNSUPPORTED = -1;
+ // TODO: find better home for these template constants
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
+ * networks together. Only uses statistics for currently active IMSI.
+ *
+ * @hide
+ */
+ public static final int TEMPLATE_MOBILE_ALL = 1;
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
+ * networks together that roughly meet a "3G" definition, or lower. Only
+ * uses statistics for currently active IMSI.
+ *
+ * @hide
+ */
+ public static final int TEMPLATE_MOBILE_3G_LOWER = 2;
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
+ * networks together that meet a "4G" definition. Only uses statistics for
+ * currently active IMSI.
+ *
+ * @hide
+ */
+ public static final int TEMPLATE_MOBILE_4G = 3;
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_WIFI} style
+ * networks together.
+ *
+ * @hide
+ */
+ public static final int TEMPLATE_WIFI = 4;
+
+ /**
+ * Snapshot of {@link NetworkStats} when the currently active profiling
+ * session started, or {@code null} if no session active.
+ *
+ * @see #startDataProfiling(Context)
+ * @see #stopDataProfiling(Context)
+ */
+ private static NetworkStats sActiveProfilingStart;
+
+ private static Object sProfilingLock = new Object();
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ */
+ public static void setThreadStatsTag(String tag) {
+ BlockGuard.setThreadSocketStatsTag(tag);
+ }
+
+ public static void clearThreadStatsTag() {
+ BlockGuard.setThreadSocketStatsTag(null);
+ }
+
+ /**
+ * Set specific UID to use when accounting {@link Socket} traffic
+ * originating from the current thread. Designed for use when performing an
+ * operation on behalf of another application.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ * <p>
+ * To take effect, caller must hold
+ * {@link android.Manifest.permission#UPDATE_DEVICE_STATS} permission.
+ *
+ * {@hide}
+ */
+ public static void setThreadStatsUid(int uid) {
+ BlockGuard.setThreadSocketStatsUid(uid);
+ }
+
+ /** {@hide} */
+ public static void clearThreadStatsUid() {
+ BlockGuard.setThreadSocketStatsUid(-1);
+ }
+
+ /**
+ * Tag the given {@link Socket} with any statistics parameters active for
+ * the current thread. Subsequent calls always replace any existing
+ * parameters. When finished, call {@link #untagSocket(Socket)} to remove
+ * statistics parameters.
+ *
+ * @see #setThreadStatsTag(String)
+ * @see #setThreadStatsUid(int)
+ */
+ public static void tagSocket(Socket socket) throws SocketException {
+ BlockGuard.tagSocketFd(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link Socket}.
+ */
+ public static void untagSocket(Socket socket) throws SocketException {
+ BlockGuard.untagSocketFd(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Start profiling data usage for current UID. Only one profiling session
+ * can be active at a time.
+ *
+ * @hide
+ */
+ public static void startDataProfiling(Context context) {
+ synchronized (sProfilingLock) {
+ if (sActiveProfilingStart != null) {
+ throw new IllegalStateException("already profiling data");
+ }
+
+ // take snapshot in time; we calculate delta later
+ sActiveProfilingStart = getNetworkStatsForUid(context);
+ }
+ }
+
+ /**
+ * Stop profiling data usage for current UID.
+ *
+ * @return Detailed {@link NetworkStats} of data that occurred since last
+ * {@link #startDataProfiling(Context)} call.
+ * @hide
+ */
+ public static NetworkStats stopDataProfiling(Context context) {
+ synchronized (sProfilingLock) {
+ if (sActiveProfilingStart == null) {
+ throw new IllegalStateException("not profiling data");
+ }
+
+ // subtract starting values and return delta
+ final NetworkStats profilingStop = getNetworkStatsForUid(context);
+ final NetworkStats profilingDelta = profilingStop.subtractClamped(
+ sActiveProfilingStart);
+ sActiveProfilingStart = null;
+ return profilingDelta;
+ }
+ }
+
/**
* Get the total number of packets transmitted through the mobile interface.
*
@@ -294,4 +442,21 @@ public class TrafficStats {
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getUidUdpRxPackets(int uid);
+
+ /**
+ * Return detailed {@link NetworkStats} for the current UID. Requires no
+ * special permission.
+ */
+ private static NetworkStats getNetworkStatsForUid(Context context) {
+ final IBinder binder = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ final INetworkManagementService service = INetworkManagementService.Stub.asInterface(
+ binder);
+
+ final int uid = android.os.Process.myUid();
+ try {
+ return service.getNetworkStatsUidDetail(uid);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/core/java/android/net/http/Headers.java b/core/java/android/net/http/Headers.java
index 74c0de8..657e071 100644
--- a/core/java/android/net/http/Headers.java
+++ b/core/java/android/net/http/Headers.java
@@ -16,7 +16,6 @@
package android.net.http;
-import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
@@ -201,7 +200,7 @@ public final class Headers {
try {
contentLength = Long.parseLong(val);
} catch (NumberFormatException e) {
- if (Config.LOGV) {
+ if (false) {
Log.v(LOGTAG, "Headers.headers(): error parsing"
+ " content length: " + buffer.toString());
}
@@ -449,7 +448,7 @@ public final class Headers {
}
int extraLen = mExtraHeaderNames.size();
for (int i = 0; i < extraLen; i++) {
- if (Config.LOGV) {
+ if (false) {
HttpLog.v("Headers.getHeaders() extra: " + i + " " +
mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
}
diff --git a/core/java/android/net/http/HttpLog.java b/core/java/android/net/http/HttpLog.java
index 30bf647..0934664 100644
--- a/core/java/android/net/http/HttpLog.java
+++ b/core/java/android/net/http/HttpLog.java
@@ -23,7 +23,6 @@ package android.net.http;
import android.os.SystemClock;
import android.util.Log;
-import android.util.Config;
/**
* {@hide}
@@ -32,7 +31,7 @@ class HttpLog {
private final static String LOGTAG = "http";
private static final boolean DEBUG = false;
- static final boolean LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ static final boolean LOGV = false;
static void v(String logMe) {
Log.v(LOGTAG, SystemClock.uptimeMillis() + " " + Thread.currentThread().getName() + " " + logMe);
diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java
new file mode 100644
index 0000000..b5d64e4
--- /dev/null
+++ b/core/java/android/net/http/HttpResponseCache.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import android.content.Context;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import libcore.io.DiskLruCache;
+import libcore.io.IoUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+/**
+ * Caches HTTP and HTTPS responses to the filesystem so they may be reused,
+ * saving time and bandwidth. This class supports {@link HttpURLConnection} and
+ * {@link HttpsURLConnection}; there is no platform-provided cache for {@link
+ * DefaultHttpClient} or {@link AndroidHttpClient}.
+ *
+ * <h3>Installing an HTTP response cache</h3>
+ * Enable caching of all of your application's HTTP requests by installing the
+ * cache at application startup. For example, this code installs a 10 MiB cache
+ * in the {@link Context#getCacheDir() application-specific cache directory} of
+ * the filesystem}: <pre> {@code
+ * protected void onCreate(Bundle savedInstanceState) {
+ * ...
+ *
+ * try {
+ * File httpCacheDir = new File(context.getCacheDir(), "http");
+ * long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
+ * HttpResponseCache.install(httpCacheDir, httpCacheSize);
+ * } catch (IOException e) {
+ * Log.i(TAG, "HTTP response cache installation failed:" + e);
+ * }
+ * }
+ *
+ * protected void onStop() {
+ * ...
+ *
+ * HttpResponseCache cache = HttpResponseCache.getInstalled();
+ * if (cache != null) {
+ * cache.flush();
+ * }
+ * }}</pre>
+ * This cache will evict entries as necessary to keep its size from exceeding
+ * 10 MiB. The best cache size is application specific and depends on the size
+ * and frequency of the files being downloaded. Increasing the limit may improve
+ * the hit rate, but it may also just waste filesystem space!
+ *
+ * <p>For some applications it may be preferable to create the cache in the
+ * external storage directory. Although it often has more free space, external
+ * storage is optional and&#8212;even if available&#8212;can disappear during
+ * use. Retrieve the external cache directory using {@link Context#getExternalCacheDir()}. If this method
+ * returns null, your application should fall back to either not caching or
+ * caching on non-external storage. If the external storage is removed during
+ * use, the cache hit rate will drop to zero and ongoing cache reads will fail.
+ *
+ * <p>Flushing the cache forces its data to the filesystem. This ensures that
+ * all responses written to the cache will be readable the next time the
+ * activity starts.
+ *
+ * <h3>Cache Optimization</h3>
+ * To measure cache effectiveness, this class tracks three statistics:
+ * <ul>
+ * <li><strong>{@link #getRequestCount() Request Count:}</strong> the number
+ * of HTTP requests issued since this cache was created.
+ * <li><strong>{@link #getNetworkCount() Network Count:}</strong> the
+ * number of those requests that required network use.
+ * <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of
+ * those requests whose responses were served by the cache.
+ * </ul>
+ * Sometimes a request will result in a conditional cache hit. If the cache
+ * contains a stale copy of the response, the client will issue a conditional
+ * {@code GET}. The server will then send either the updated response if it has
+ * changed, or a short 'not modified' response if the client's copy is still
+ * valid. Such responses increment both the network count and hit count.
+ *
+ * <p>The best way to improve the cache hit rate is by configuring the web
+ * server to return cacheable responses. Although this client honors all <a
+ * href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache
+ * headers, it doesn't cache partial responses.
+ *
+ * <h3>Force a Network Response</h3>
+ * In some situations, such as after a user clicks a 'refresh' button, it may be
+ * necessary to skip the cache, and fetch data directly from the server. To force
+ * a full refresh, add the {@code no-cache} directive: <pre> {@code
+ * connection.addRequestProperty("Cache-Control", "no-cache");
+ * }</pre>
+ * If it is only necessary to force a cached response to be validated by the
+ * server, use the more efficient {@code max-age=0} instead: <pre> {@code
+ * connection.addRequestProperty("Cache-Control", "max-age=0");
+ * }</pre>
+ *
+ * <h3>Force a Cache Response</h3>
+ * Sometimes you'll want to show resources if they are available immediately,
+ * but not otherwise. This can be used so your application can show
+ * <i>something</i> while waiting for the latest data to be downloaded. To
+ * restrict a request to locally-cached resources, add the {@code
+ * only-if-cached} directive: <pre> {@code
+ * try {
+ * connection.addRequestProperty("Cache-Control", "only-if-cached");
+ * InputStream cached = connection.getInputStream();
+ * // the resource was cached! show it
+ * } catch (FileNotFoundException e) {
+ * // the resource was not cached
+ * }
+ * }</pre>
+ * This technique works even better in situations where a stale response is
+ * better than no response. To permit stale cached responses, use the {@code
+ * max-stale} directive with the maximum staleness in seconds: <pre> {@code
+ * int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+ * connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
+ * }</pre>
+ */
+public final class HttpResponseCache extends ResponseCache implements Closeable {
+
+ private final libcore.net.http.HttpResponseCache delegate;
+
+ private HttpResponseCache(File directory, long maxSize) throws IOException {
+ this.delegate = new libcore.net.http.HttpResponseCache(directory, maxSize);
+ }
+
+ /**
+ * Returns the currently-installed {@code HttpResponseCache}, or null if
+ * there is no cache installed or it is not a {@code HttpResponseCache}.
+ */
+ public static HttpResponseCache getInstalled() {
+ ResponseCache installed = ResponseCache.getDefault();
+ return installed instanceof HttpResponseCache ? (HttpResponseCache) installed : null;
+ }
+
+ /**
+ * Creates a new HTTP response cache and {@link ResponseCache#setDefault
+ * sets it} as the system default cache.
+ *
+ * @param directory the directory to hold cache data.
+ * @param maxSize the maximum size of the cache in bytes.
+ * @return the newly-installed cache
+ * @throws IOException if {@code directory} cannot be used for this cache.
+ * Most applications should respond to this exception by logging a
+ * warning.
+ */
+ public static HttpResponseCache install(File directory, long maxSize) throws IOException {
+ HttpResponseCache installed = getInstalled();
+ if (installed != null) {
+ // don't close and reopen if an equivalent cache is already installed
+ DiskLruCache installedCache = installed.delegate.getCache();
+ if (installedCache.getDirectory().equals(directory)
+ && installedCache.maxSize() == maxSize
+ && !installedCache.isClosed()) {
+ return installed;
+ } else {
+ IoUtils.closeQuietly(installed);
+ }
+ }
+
+ HttpResponseCache result = new HttpResponseCache(directory, maxSize);
+ ResponseCache.setDefault(result);
+ return result;
+ }
+
+ @Override public CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ return delegate.get(uri, requestMethod, requestHeaders);
+ }
+
+ @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+ return delegate.put(uri, urlConnection);
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the {@link #maxSize} if a background
+ * deletion is pending.
+ */
+ public long size() {
+ return delegate.getCache().size();
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public long maxSize() {
+ return delegate.getCache().maxSize();
+ }
+
+ /**
+ * Force buffered operations to the filesystem. This ensures that responses
+ * written to the cache will be available the next time the cache is opened,
+ * even if this process is killed.
+ */
+ public void flush() {
+ try {
+ delegate.getCache().flush(); // TODO: fix flush() to not throw?
+ } catch (IOException ignored) {
+ }
+ }
+
+ /**
+ * Returns the number of HTTP requests that required the network to either
+ * supply a response or validate a locally cached response.
+ */
+ public int getNetworkCount() {
+ return delegate.getNetworkCount();
+ }
+
+ /**
+ * Returns the number of HTTP requests whose response was provided by the
+ * cache. This may include conditional {@code GET} requests that were
+ * validated over the network.
+ */
+ public int getHitCount() {
+ return delegate.getHitCount();
+ }
+
+ /**
+ * Returns the total number of HTTP requests that were made. This includes
+ * both client requests and requests that were made on the client's behalf
+ * to handle a redirects and retries.
+ */
+ public int getRequestCount() {
+ return delegate.getRequestCount();
+ }
+
+ /**
+ * Uninstalls the cache and releases any active resources. Stored contents
+ * will remain on the filesystem.
+ */
+ @Override public void close() throws IOException {
+ if (ResponseCache.getDefault() == this) {
+ ResponseCache.setDefault(null);
+ }
+ delegate.getCache().close();
+ }
+
+ /**
+ * Uninstalls the cache and deletes all of its stored contents.
+ */
+ public void delete() throws IOException {
+ if (ResponseCache.getDefault() == this) {
+ ResponseCache.setDefault(null);
+ }
+ delegate.getCache().delete();
+ }
+}
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index d77e9d9..84765a5 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -289,11 +289,9 @@ public class HttpsConnection extends Connection {
} else {
// if we do not have a proxy, we simply connect to the host
try {
- sslSock = (SSLSocket) getSocketFactory().createSocket();
-
+ sslSock = (SSLSocket) getSocketFactory().createSocket(
+ mHost.getHostName(), mHost.getPort());
sslSock.setSoTimeout(SOCKET_TIMEOUT);
- sslSock.connect(new InetSocketAddress(mHost.getHostName(),
- mHost.getPort()));
} catch(IOException e) {
if (sslSock != null) {
sslSock.close();
diff --git a/core/java/android/nfc/ErrorCodes.java b/core/java/android/nfc/ErrorCodes.java
index 69329df..3adcdc3 100644
--- a/core/java/android/nfc/ErrorCodes.java
+++ b/core/java/android/nfc/ErrorCodes.java
@@ -57,6 +57,7 @@ public class ErrorCodes {
case ERROR_SE_ALREADY_SELECTED: return "SE_ALREADY_SELECTED";
case ERROR_SE_CONNECTED: return "SE_CONNECTED";
case ERROR_NO_SE_CONNECTED: return "NO_SE_CONNECTED";
+ case ERROR_NOT_SUPPORTED: return "NOT_SUPPORTED";
default: return "UNKNOWN ERROR";
}
}
@@ -105,4 +106,6 @@ public class ErrorCodes {
public static final int ERROR_NO_SE_CONNECTED = -20;
-} \ No newline at end of file
+ public static final int ERROR_NOT_SUPPORTED = -21;
+
+}
diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl
new file mode 100644
index 0000000..80ba2ed
--- /dev/null
+++ b/core/java/android/nfc/INdefPushCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.nfc.NdefMessage;
+
+/**
+ * @hide
+ */
+interface INdefPushCallback
+{
+ NdefMessage onConnect();
+ void onMessagePushed();
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 870127c..d11fea0 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,6 +25,7 @@ import android.nfc.TechListParcel;
import android.nfc.ILlcpSocket;
import android.nfc.ILlcpServiceSocket;
import android.nfc.ILlcpConnectionlessSocket;
+import android.nfc.INdefPushCallback;
import android.nfc.INfcTag;
import android.nfc.IP2pTarget;
import android.nfc.IP2pInitiator;
@@ -51,6 +52,7 @@ interface INfcAdapter
in IntentFilter[] filters, in TechListParcel techLists);
void disableForegroundDispatch(in ComponentName activity);
void enableForegroundNdefPush(in ComponentName activity, in NdefMessage msg);
+ void enableForegroundNdefPushWithCallback(in ComponentName activity, in INdefPushCallback callback);
void disableForegroundNdefPush(in ComponentName activity);
// Non-public methods
diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl
index 57dc38c..b66035f 100644
--- a/core/java/android/nfc/INfcTag.aidl
+++ b/core/java/android/nfc/INfcTag.aidl
@@ -17,6 +17,7 @@
package android.nfc;
import android.nfc.NdefMessage;
+import android.nfc.Tag;
import android.nfc.TransceiveResult;
/**
@@ -40,7 +41,9 @@ interface INfcTag
int ndefMakeReadOnly(int nativeHandle);
boolean ndefIsWritable(int nativeHandle);
int formatNdef(int nativeHandle, in byte[] key);
+ Tag rediscover(int nativehandle);
void setIsoDepTimeout(int timeout);
- void resetIsoDepTimeout();
+ void setFelicaTimeout(int timeout);
+ void resetTimeouts();
}
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 746d3df..3fd26dd 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -16,10 +16,13 @@
package android.nfc;
+import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.UnsupportedOperationException;
+import java.nio.charset.Charsets;
+import java.util.Arrays;
/**
* Represents a logical (unchunked) NDEF (NFC Data Exchange Format) record.
@@ -142,6 +145,50 @@ public final class NdefRecord implements Parcelable {
private static final byte FLAG_SR = (byte) 0x10;
private static final byte FLAG_IL = (byte) 0x08;
+ /**
+ * NFC Forum "URI Record Type Definition"
+ *
+ * This is a mapping of "URI Identifier Codes" to URI string prefixes,
+ * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
+ */
+ private static final String[] URI_PREFIX_MAP = new String[] {
+ "", // 0x00
+ "http://www.", // 0x01
+ "https://www.", // 0x02
+ "http://", // 0x03
+ "https://", // 0x04
+ "tel:", // 0x05
+ "mailto:", // 0x06
+ "ftp://anonymous:anonymous@", // 0x07
+ "ftp://ftp.", // 0x08
+ "ftps://", // 0x09
+ "sftp://", // 0x0A
+ "smb://", // 0x0B
+ "nfs://", // 0x0C
+ "ftp://", // 0x0D
+ "dav://", // 0x0E
+ "news:", // 0x0F
+ "telnet://", // 0x10
+ "imap:", // 0x11
+ "rtsp://", // 0x12
+ "urn:", // 0x13
+ "pop:", // 0x14
+ "sip:", // 0x15
+ "sips:", // 0x16
+ "tftp:", // 0x17
+ "btspp://", // 0x18
+ "btl2cap://", // 0x19
+ "btgoep://", // 0x1A
+ "tcpobex://", // 0x1B
+ "irdaobex://", // 0x1C
+ "file://", // 0x1D
+ "urn:epc:id:", // 0x1E
+ "urn:epc:tag:", // 0x1F
+ "urn:epc:pat:", // 0x20
+ "urn:epc:raw:", // 0x21
+ "urn:epc:", // 0x22
+ };
+
private final byte mFlags;
private final short mTnf;
private final byte[] mType;
@@ -163,6 +210,18 @@ public final class NdefRecord implements Parcelable {
* must not be null
*/
public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
+ /* New NDEF records created by applications will have FLAG_MB|FLAG_ME
+ * set by default; when multiple records are stored in a
+ * {@link NdefMessage}, these flags will be corrected when the {@link NdefMessage}
+ * is serialized to bytes.
+ */
+ this(tnf, type, id, payload, (byte)(FLAG_MB|FLAG_ME));
+ }
+
+ /**
+ * @hide
+ */
+ /*package*/ NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload, byte flags) {
/* check arguments */
if ((type == null) || (id == null) || (payload == null)) {
throw new IllegalArgumentException("Illegal null argument");
@@ -172,9 +231,6 @@ public final class NdefRecord implements Parcelable {
throw new IllegalArgumentException("TNF out of range " + tnf);
}
- /* generate flag */
- byte flags = FLAG_MB | FLAG_ME;
-
/* Determine if it is a short record */
if(payload.length < 0xFF) {
flags |= FLAG_SR;
@@ -247,6 +303,50 @@ public final class NdefRecord implements Parcelable {
}
/**
+ * Helper to return the NdefRecord as a URI.
+ * TODO: Consider making a member method instead of static
+ * TODO: Consider more validation that this is a URI record
+ * TODO: Make a public API
+ * @hide
+ */
+ public static Uri parseWellKnownUriRecord(NdefRecord record) throws FormatException {
+ byte[] payload = record.getPayload();
+ if (payload.length < 2) {
+ throw new FormatException("Payload is not a valid URI (missing prefix)");
+ }
+
+ /*
+ * payload[0] contains the URI Identifier Code, per the
+ * NFC Forum "URI Record Type Definition" section 3.2.2.
+ *
+ * payload[1]...payload[payload.length - 1] contains the rest of
+ * the URI.
+ */
+ int prefixIndex = (payload[0] & 0xff);
+ if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
+ throw new FormatException("Payload is not a valid URI (invalid prefix)");
+ }
+ String prefix = URI_PREFIX_MAP[prefixIndex];
+ byte[] fullUri = concat(prefix.getBytes(Charsets.UTF_8),
+ Arrays.copyOfRange(payload, 1, payload.length));
+ return Uri.parse(new String(fullUri, Charsets.UTF_8));
+ }
+
+ private static byte[] concat(byte[]... arrays) {
+ int length = 0;
+ for (byte[] array : arrays) {
+ length += array.length;
+ }
+ byte[] result = new byte[length];
+ int pos = 0;
+ for (byte[] array : arrays) {
+ System.arraycopy(array, 0, result, pos, array.length);
+ pos += array.length;
+ }
+ return result;
+ }
+
+ /**
* Returns this entire NDEF Record as a byte array.
*/
public byte[] toByteArray() {
@@ -258,6 +358,7 @@ public final class NdefRecord implements Parcelable {
}
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFlags);
dest.writeInt(mTnf);
dest.writeInt(mType.length);
dest.writeByteArray(mType);
@@ -270,6 +371,7 @@ public final class NdefRecord implements Parcelable {
public static final Parcelable.Creator<NdefRecord> CREATOR =
new Parcelable.Creator<NdefRecord>() {
public NdefRecord createFromParcel(Parcel in) {
+ byte flags = (byte)in.readInt();
short tnf = (short)in.readInt();
int typeLength = in.readInt();
byte[] type = new byte[typeLength];
@@ -281,7 +383,7 @@ public final class NdefRecord implements Parcelable {
byte[] payload = new byte[payloadLength];
in.readByteArray(payload);
- return new NdefRecord(tnf, type, id, payload);
+ return new NdefRecord(tnf, type, id, payload, flags);
}
public NdefRecord[] newArray(int size) {
return new NdefRecord[size];
@@ -290,4 +392,4 @@ public final class NdefRecord implements Parcelable {
private native int parseNdefRecord(byte[] data);
private native byte[] generate(short flags, short tnf, byte[] type, byte[] id, byte[] data);
-} \ No newline at end of file
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 4689804..738e75f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -124,7 +124,7 @@ public final class NfcAdapter {
* Intent to start an activity when a tag is discovered.
*
* <p>This intent will not be started when a tag is discovered if any activities respond to
- * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
+ * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
@@ -235,6 +235,37 @@ public final class NfcAdapter {
*/
private static final int DISCOVERY_MODE_CARD_EMULATION = 2;
+ /**
+ * Callback passed into {@link #enableForegroundNdefPush(Activity,NdefPushCallback)}. This
+ */
+ public interface NdefPushCallback {
+ /**
+ * Called when a P2P connection is created.
+ */
+ NdefMessage createMessage();
+ /**
+ * Called when the message is pushed.
+ */
+ void onMessagePushed();
+ }
+
+ private static class NdefPushCallbackWrapper extends INdefPushCallback.Stub {
+ private NdefPushCallback mCallback;
+
+ public NdefPushCallbackWrapper(NdefPushCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public NdefMessage onConnect() {
+ return mCallback.createMessage();
+ }
+
+ @Override
+ public void onMessagePushed() {
+ mCallback.onMessagePushed();
+ }
+ }
// Guarded by NfcAdapter.class
private static boolean sIsInitialized = false;
@@ -575,6 +606,44 @@ public final class NfcAdapter {
}
/**
+ * Enable NDEF message push over P2P while this Activity is in the foreground.
+ *
+ * <p>For this to function properly the other NFC device being scanned must
+ * support the "com.android.npp" NDEF push protocol. Support for this
+ * protocol is currently optional for Android NFC devices.
+ *
+ * <p>This method must be called from the main thread.
+ *
+ * <p class="note"><em>NOTE:</em> While foreground NDEF push is active standard tag dispatch is disabled.
+ * Only the foreground activity may receive tag discovered dispatches via
+ * {@link #enableForegroundDispatch}.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param activity the foreground Activity
+ * @param callback is called on when the P2P connection is established
+ * @throws IllegalStateException if the Activity is not currently in the foreground
+ * @throws OperationNotSupportedException if this Android device does not support NDEF push
+ */
+ public void enableForegroundNdefPush(Activity activity, NdefPushCallback callback) {
+ if (activity == null || callback == null) {
+ throw new NullPointerException();
+ }
+ if (!activity.isResumed()) {
+ throw new IllegalStateException("Foregorund NDEF push can only be enabled " +
+ "when your activity is resumed");
+ }
+ try {
+ ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
+ mForegroundNdefPushListener);
+ sService.enableForegroundNdefPushWithCallback(activity.getComponentName(),
+ new NdefPushCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
* Disable NDEF message push over P2P.
*
* <p>After calling {@link #enableForegroundNdefPush}, an activity
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index b676975..54583d6 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -30,7 +30,9 @@ import android.nfc.tech.TagTechnology;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import java.io.IOException;
import java.util.Arrays;
/**
@@ -233,6 +235,50 @@ public final class Tag implements Parcelable {
return mTechStringList;
}
+ /**
+ * Rediscover the technologies available on this tag.
+ * <p>
+ * The technologies that are available on a tag may change due to
+ * operations being performed on a tag. For example, formatting a
+ * tag as NDEF adds the {@link Ndef} technology. The {@link rediscover}
+ * method reenumerates the available technologies on the tag
+ * and returns a new {@link Tag} object containing these technologies.
+ * <p>
+ * You may not be connected to any of this {@link Tag}'s technologies
+ * when calling this method.
+ * This method guarantees that you will be returned the same Tag
+ * if it is still in the field.
+ * <p>May cause RF activity and may block. Must not be called
+ * from the main application thread. A blocked call will be canceled with
+ * {@link IOException} by calling {@link #close} from another thread.
+ * <p>Does not remove power from the RF field, so a tag having a random
+ * ID should not change its ID.
+ * @return the rediscovered tag object.
+ * @throws IOException if the tag cannot be rediscovered
+ * @hide
+ */
+ // TODO See if we need TagLostException
+ // TODO Unhide for ICS
+ // TODO Update documentation to make sure it matches with the final
+ // implementation.
+ public Tag rediscover() throws IOException {
+ if (getConnectedTechnology() != -1) {
+ throw new IllegalStateException("Close connection to the technology first!");
+ }
+
+ try {
+ Tag newTag = mTagService.rediscover(getServiceHandle());
+ if (newTag != null) {
+ return newTag;
+ } else {
+ throw new IOException("Failed to rediscover tag");
+ }
+ } catch (RemoteException e) {
+ throw new IOException("NFC service dead");
+ }
+ }
+
+
/** @hide */
public boolean hasTech(int techType) {
for (int tech : mTechList) {
diff --git a/core/java/android/nfc/tech/BasicTagTechnology.java b/core/java/android/nfc/tech/BasicTagTechnology.java
index 7ec807a..bcb7199 100644
--- a/core/java/android/nfc/tech/BasicTagTechnology.java
+++ b/core/java/android/nfc/tech/BasicTagTechnology.java
@@ -77,6 +77,10 @@ import java.io.IOException;
// Store this in the tag object
mTag.setConnectedTechnology(mSelectedTechnology);
mIsConnected = true;
+ } else if (errorCode == ErrorCodes.ERROR_NOT_SUPPORTED) {
+ throw new UnsupportedOperationException("Connecting to " +
+ "this technology is not supported by the NFC " +
+ "adapter.");
} else {
throw new IOException();
}
@@ -115,6 +119,7 @@ import java.io.IOException;
/* Note that we don't want to physically disconnect the tag,
* but just reconnect to it to reset its state
*/
+ mTag.getTagService().resetTimeouts();
mTag.getTagService().reconnect(mTag.getServiceHandle());
} catch (RemoteException e) {
Log.e(TAG, "NFC service dead", e);
diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java
index 9c3074b..38b2bbd 100644
--- a/core/java/android/nfc/tech/IsoDep.java
+++ b/core/java/android/nfc/tech/IsoDep.java
@@ -96,16 +96,6 @@ public final class IsoDep extends BasicTagTechnology {
}
}
- @Override
- public void close() throws IOException {
- try {
- mTag.getTagService().resetIsoDepTimeout();
- } catch (RemoteException e) {
- Log.e(TAG, "NFC service dead", e);
- }
- super.close();
- }
-
/**
* Return the ISO-DEP historical bytes for {@link NfcA} tags.
* <p>Does not cause any RF activity and does not block.
diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/core/java/android/nfc/tech/MifareUltralight.java
index 7a6e79c..6c2754b 100644
--- a/core/java/android/nfc/tech/MifareUltralight.java
+++ b/core/java/android/nfc/tech/MifareUltralight.java
@@ -18,6 +18,7 @@ package android.nfc.tech;
import android.nfc.Tag;
import android.nfc.TagLostException;
+import android.os.Bundle;
import android.os.RemoteException;
import java.io.IOException;
@@ -69,6 +70,9 @@ public final class MifareUltralight extends BasicTagTechnology {
private static final int NXP_MANUFACTURER_ID = 0x04;
private static final int MAX_PAGE_COUNT = 256;
+ /** @hide */
+ public static final String EXTRA_IS_UL_C = "isulc";
+
private int mType;
/**
@@ -101,10 +105,12 @@ public final class MifareUltralight extends BasicTagTechnology {
mType = TYPE_UNKNOWN;
if (a.getSak() == 0x00 && tag.getId()[0] == NXP_MANUFACTURER_ID) {
- // could be UL or UL-C
- //TODO: stack should use NXP AN1303 procedure to make a best guess
- // attempt at classifying Ultralight vs Ultralight C.
- mType = TYPE_ULTRALIGHT;
+ Bundle extras = tag.getTechExtras(TagTechnology.MIFARE_ULTRALIGHT);
+ if (extras.getBoolean(EXTRA_IS_UL_C)) {
+ mType = TYPE_ULTRALIGHT_C;
+ } else {
+ mType = TYPE_ULTRALIGHT;
+ }
}
}
diff --git a/core/java/android/nfc/tech/NfcF.java b/core/java/android/nfc/tech/NfcF.java
index e0ebbe8..250c9b3 100644
--- a/core/java/android/nfc/tech/NfcF.java
+++ b/core/java/android/nfc/tech/NfcF.java
@@ -19,6 +19,7 @@ package android.nfc.tech;
import android.nfc.Tag;
import android.os.Bundle;
import android.os.RemoteException;
+import android.util.Log;
import java.io.IOException;
@@ -33,6 +34,8 @@ import java.io.IOException;
* require the {@link android.Manifest.permission#NFC} permission.
*/
public final class NfcF extends BasicTagTechnology {
+ private static final String TAG = "NFC";
+
/** @hide */
public static final String EXTRA_SC = "systemcode";
/** @hide */
@@ -111,4 +114,26 @@ public final class NfcF extends BasicTagTechnology {
public byte[] transceive(byte[] data) throws IOException {
return transceive(data, true);
}
+
+ /**
+ * Set the timeout of {@link #transceive} in milliseconds.
+ * <p>The timeout only applies to NfcF {@link #transceive}, and is
+ * reset to a default value when {@link #close} is called.
+ * <p>Setting a longer timeout may be useful when performing
+ * transactions that require a long processing time on the tag
+ * such as key generation.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param timeout timeout value in milliseconds
+ * @hide
+ */
+ // TODO Unhide for ICS
+ public void setTimeout(int timeout) {
+ try {
+ mTag.getTagService().setFelicaTimeout(timeout);
+ } catch (RemoteException e) {
+ Log.e(TAG, "NFC service dead", e);
+ }
+ }
}
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 1803604..64bba54 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -153,7 +153,6 @@ public abstract class AsyncTask<Params, Progress, Result> {
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
-
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@@ -183,6 +182,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
private static final InternalHandler sHandler = new InternalHandler();
+ private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
@@ -240,6 +240,11 @@ public abstract class AsyncTask<Params, Progress, Result> {
sHandler.getLooper();
}
+ /** @hide */
+ public static void setDefaultExecutor(Executor exec) {
+ sDefaultExecutor = exec;
+ }
+
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
@@ -496,7 +501,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*/
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
- return executeOnExecutor(THREAD_POOL_EXECUTOR, params);
+ return executeOnExecutor(sDefaultExecutor, params);
}
/**
@@ -559,7 +564,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
* a simple Runnable object.
*/
public static void execute(Runnable runnable) {
- THREAD_POOL_EXECUTOR.execute(runnable);
+ sDefaultExecutor.execute(runnable);
}
/**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1d6bc4e..e344197 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -26,6 +26,7 @@ import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
import android.util.Log;
import android.util.Printer;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -408,15 +409,19 @@ public abstract class BatteryStats implements Parcelable {
}
public final static class HistoryItem implements Parcelable {
+ static final String TAG = "HistoryItem";
+ static final boolean DEBUG = false;
+
public HistoryItem next;
public long time;
- public static final byte CMD_UPDATE = 0;
- public static final byte CMD_START = 1;
- public static final byte CMD_OVERFLOW = 2;
+ public static final byte CMD_NULL = 0;
+ public static final byte CMD_UPDATE = 1;
+ public static final byte CMD_START = 2;
+ public static final byte CMD_OVERFLOW = 3;
- public byte cmd;
+ public byte cmd = CMD_NULL;
public byte batteryLevel;
public byte batteryStatus;
@@ -427,33 +432,38 @@ public abstract class BatteryStats implements Parcelable {
public char batteryVoltage;
// Constants from SCREEN_BRIGHTNESS_*
- public static final int STATE_BRIGHTNESS_MASK = 0x000000f;
+ public static final int STATE_BRIGHTNESS_MASK = 0x0000000f;
public static final int STATE_BRIGHTNESS_SHIFT = 0;
// Constants from SIGNAL_STRENGTH_*
- public static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0;
+ public static final int STATE_SIGNAL_STRENGTH_MASK = 0x000000f0;
public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4;
// Constants from ServiceState.STATE_*
- public static final int STATE_PHONE_STATE_MASK = 0x0000f00;
+ public static final int STATE_PHONE_STATE_MASK = 0x00000f00;
public static final int STATE_PHONE_STATE_SHIFT = 8;
// Constants from DATA_CONNECTION_*
- public static final int STATE_DATA_CONNECTION_MASK = 0x000f000;
+ public static final int STATE_DATA_CONNECTION_MASK = 0x0000f000;
public static final int STATE_DATA_CONNECTION_SHIFT = 12;
- public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30;
- public static final int STATE_SCREEN_ON_FLAG = 1<<29;
+ // These states always appear directly in the first int token
+ // of a delta change; they should be ones that change relatively
+ // frequently.
+ public static final int STATE_WAKE_LOCK_FLAG = 1<<30;
+ public static final int STATE_SENSOR_ON_FLAG = 1<<29;
public static final int STATE_GPS_ON_FLAG = 1<<28;
- public static final int STATE_PHONE_IN_CALL_FLAG = 1<<27;
- public static final int STATE_PHONE_SCANNING_FLAG = 1<<26;
- public static final int STATE_WIFI_ON_FLAG = 1<<25;
- public static final int STATE_WIFI_RUNNING_FLAG = 1<<24;
- public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<23;
- public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<22;
- public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<21;
- public static final int STATE_BLUETOOTH_ON_FLAG = 1<<20;
- public static final int STATE_AUDIO_ON_FLAG = 1<<19;
- public static final int STATE_VIDEO_ON_FLAG = 1<<18;
- public static final int STATE_WAKE_LOCK_FLAG = 1<<17;
- public static final int STATE_SENSOR_ON_FLAG = 1<<16;
+ public static final int STATE_PHONE_SCANNING_FLAG = 1<<27;
+ public static final int STATE_WIFI_RUNNING_FLAG = 1<<26;
+ public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25;
+ public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<24;
+ public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23;
+ // These are on the lower bits used for the command; if they change
+ // we need to write another int of data.
+ public static final int STATE_AUDIO_ON_FLAG = 1<<22;
+ public static final int STATE_VIDEO_ON_FLAG = 1<<21;
+ public static final int STATE_SCREEN_ON_FLAG = 1<<20;
+ public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
+ public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
+ public static final int STATE_WIFI_ON_FLAG = 1<<17;
+ public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
public static final int MOST_INTERESTING_STATES =
STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG
@@ -466,16 +476,7 @@ public abstract class BatteryStats implements Parcelable {
public HistoryItem(long time, Parcel src) {
this.time = time;
- int bat = src.readInt();
- cmd = (byte)(bat&0xff);
- batteryLevel = (byte)((bat>>8)&0xff);
- batteryStatus = (byte)((bat>>16)&0xf);
- batteryHealth = (byte)((bat>>20)&0xf);
- batteryPlugType = (byte)((bat>>24)&0xf);
- bat = src.readInt();
- batteryTemperature = (char)(bat&0xffff);
- batteryVoltage = (char)((bat>>16)&0xffff);
- states = src.readInt();
+ readFromParcel(src);
}
public int describeContents() {
@@ -495,6 +496,174 @@ public abstract class BatteryStats implements Parcelable {
dest.writeInt(bat);
dest.writeInt(states);
}
+
+ private void readFromParcel(Parcel src) {
+ int bat = src.readInt();
+ cmd = (byte)(bat&0xff);
+ batteryLevel = (byte)((bat>>8)&0xff);
+ batteryStatus = (byte)((bat>>16)&0xf);
+ batteryHealth = (byte)((bat>>20)&0xf);
+ batteryPlugType = (byte)((bat>>24)&0xf);
+ bat = src.readInt();
+ batteryTemperature = (char)(bat&0xffff);
+ batteryVoltage = (char)((bat>>16)&0xffff);
+ states = src.readInt();
+ }
+
+ // Part of initial delta int that specifies the time delta.
+ static final int DELTA_TIME_MASK = 0x3ffff;
+ static final int DELTA_TIME_ABS = 0x3fffd; // Following is an entire abs update.
+ static final int DELTA_TIME_INT = 0x3fffe; // The delta is a following int
+ static final int DELTA_TIME_LONG = 0x3ffff; // The delta is a following long
+ // Part of initial delta int holding the command code.
+ static final int DELTA_CMD_MASK = 0x3;
+ static final int DELTA_CMD_SHIFT = 18;
+ // Flag in delta int: a new battery level int follows.
+ static final int DELTA_BATTERY_LEVEL_FLAG = 1<<20;
+ // Flag in delta int: a new full state and battery status int follows.
+ static final int DELTA_STATE_FLAG = 1<<21;
+ static final int DELTA_STATE_MASK = 0xffc00000;
+
+ public void writeDelta(Parcel dest, HistoryItem last) {
+ if (last == null || last.cmd != CMD_UPDATE) {
+ dest.writeInt(DELTA_TIME_ABS);
+ writeToParcel(dest, 0);
+ return;
+ }
+
+ final long deltaTime = time - last.time;
+ final int lastBatteryLevelInt = last.buildBatteryLevelInt();
+ final int lastStateInt = last.buildStateInt();
+
+ int deltaTimeToken;
+ if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+ deltaTimeToken = DELTA_TIME_LONG;
+ } else if (deltaTime >= DELTA_TIME_ABS) {
+ deltaTimeToken = DELTA_TIME_INT;
+ } else {
+ deltaTimeToken = (int)deltaTime;
+ }
+ int firstToken = deltaTimeToken
+ | (cmd<<DELTA_CMD_SHIFT)
+ | (states&DELTA_STATE_MASK);
+ final int batteryLevelInt = buildBatteryLevelInt();
+ final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+ if (batteryLevelIntChanged) {
+ firstToken |= DELTA_BATTERY_LEVEL_FLAG;
+ }
+ final int stateInt = buildStateInt();
+ final boolean stateIntChanged = stateInt != lastStateInt;
+ if (stateIntChanged) {
+ firstToken |= DELTA_STATE_FLAG;
+ }
+ dest.writeInt(firstToken);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTime=" + deltaTime);
+
+ if (deltaTimeToken >= DELTA_TIME_INT) {
+ if (deltaTimeToken == DELTA_TIME_INT) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
+ dest.writeInt((int)deltaTime);
+ } else {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+ dest.writeLong(deltaTime);
+ }
+ }
+ if (batteryLevelIntChanged) {
+ dest.writeInt(batteryLevelInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + batteryLevel
+ + " batteryTemp=" + (int)batteryTemperature
+ + " batteryVolt=" + (int)batteryVoltage);
+ }
+ if (stateIntChanged) {
+ dest.writeInt(stateInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + batteryStatus
+ + " batteryHealth=" + batteryHealth
+ + " batteryPlugType=" + batteryPlugType
+ + " states=0x" + Integer.toHexString(states));
+ }
+ }
+
+ private int buildBatteryLevelInt() {
+ return ((((int)batteryLevel)<<24)&0xff000000)
+ | ((((int)batteryTemperature)<<14)&0x00ffc000)
+ | (((int)batteryVoltage)&0x00003fff);
+ }
+
+ private int buildStateInt() {
+ return ((((int)batteryStatus)<<28)&0xf0000000)
+ | ((((int)batteryHealth)<<24)&0x0f000000)
+ | ((((int)batteryPlugType)<<22)&0x00c00000)
+ | (states&(~DELTA_STATE_MASK));
+ }
+
+ public void readDelta(Parcel src) {
+ int firstToken = src.readInt();
+ int deltaTimeToken = firstToken&DELTA_TIME_MASK;
+ cmd = (byte)((firstToken>>DELTA_CMD_SHIFT)&DELTA_CMD_MASK);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTimeToken=" + deltaTimeToken);
+
+ if (deltaTimeToken < DELTA_TIME_ABS) {
+ time += deltaTimeToken;
+ } else if (deltaTimeToken == DELTA_TIME_ABS) {
+ time = src.readLong();
+ readFromParcel(src);
+ return;
+ } else if (deltaTimeToken == DELTA_TIME_INT) {
+ int delta = src.readInt();
+ time += delta;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
+ } else {
+ long delta = src.readLong();
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
+ time += delta;
+ }
+
+ if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
+ int batteryLevelInt = src.readInt();
+ batteryLevel = (byte)((batteryLevelInt>>24)&0xff);
+ batteryTemperature = (char)((batteryLevelInt>>14)&0x3ff);
+ batteryVoltage = (char)(batteryLevelInt&0x3fff);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + batteryLevel
+ + " batteryTemp=" + (int)batteryTemperature
+ + " batteryVolt=" + (int)batteryVoltage);
+ }
+
+ if ((firstToken&DELTA_STATE_FLAG) != 0) {
+ int stateInt = src.readInt();
+ states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK));
+ batteryStatus = (byte)((stateInt>>28)&0xf);
+ batteryHealth = (byte)((stateInt>>24)&0xf);
+ batteryPlugType = (byte)((stateInt>>22)&0x3);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + batteryStatus
+ + " batteryHealth=" + batteryHealth
+ + " batteryPlugType=" + batteryPlugType
+ + " states=0x" + Integer.toHexString(states));
+ } else {
+ states = (firstToken&DELTA_STATE_MASK) | (states&(~DELTA_STATE_MASK));
+ }
+ }
+
+ public void clear() {
+ time = 0;
+ cmd = CMD_NULL;
+ batteryLevel = 0;
+ batteryStatus = 0;
+ batteryHealth = 0;
+ batteryPlugType = 0;
+ batteryTemperature = 0;
+ batteryVoltage = 0;
+ states = 0;
+ }
public void setTo(HistoryItem o) {
time = o.time;
@@ -556,11 +725,14 @@ public abstract class BatteryStats implements Parcelable {
public abstract boolean getNextHistoryLocked(HistoryItem out);
- /**
- * Return the current history of battery state changes.
- */
- public abstract HistoryItem getHistory();
-
+ public abstract void finishIteratingHistoryLocked();
+
+ public abstract boolean startIteratingOldHistoryLocked();
+
+ public abstract boolean getNextOldHistoryLocked(HistoryItem out);
+
+ public abstract void finishIteratingOldHistoryLocked();
+
/**
* Return the base time offset for the battery history.
*/
@@ -1729,7 +1901,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
+ static void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
int diff = oldval ^ newval;
if (diff == 0) return;
for (int i=0; i<descriptions.length; i++) {
@@ -1753,6 +1925,125 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ public void prepareForDumpLocked() {
+ }
+
+ public static class HistoryPrinter {
+ int oldState = 0;
+ int oldStatus = -1;
+ int oldHealth = -1;
+ int oldPlug = -1;
+ int oldTemp = -1;
+ int oldVolt = -1;
+
+ public void printNextItem(PrintWriter pw, HistoryItem rec, long now) {
+ pw.print(" ");
+ TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+ pw.print(" ");
+ if (rec.cmd == HistoryItem.CMD_START) {
+ pw.println(" START");
+ } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
+ pw.println(" *OVERFLOW*");
+ } else {
+ if (rec.batteryLevel < 10) pw.print("00");
+ else if (rec.batteryLevel < 100) pw.print("0");
+ pw.print(rec.batteryLevel);
+ pw.print(" ");
+ if (rec.states < 0x10) pw.print("0000000");
+ else if (rec.states < 0x100) pw.print("000000");
+ else if (rec.states < 0x1000) pw.print("00000");
+ else if (rec.states < 0x10000) pw.print("0000");
+ else if (rec.states < 0x100000) pw.print("000");
+ else if (rec.states < 0x1000000) pw.print("00");
+ else if (rec.states < 0x10000000) pw.print("0");
+ pw.print(Integer.toHexString(rec.states));
+ if (oldStatus != rec.batteryStatus) {
+ oldStatus = rec.batteryStatus;
+ pw.print(" status=");
+ switch (oldStatus) {
+ case BatteryManager.BATTERY_STATUS_UNKNOWN:
+ pw.print("unknown");
+ break;
+ case BatteryManager.BATTERY_STATUS_CHARGING:
+ pw.print("charging");
+ break;
+ case BatteryManager.BATTERY_STATUS_DISCHARGING:
+ pw.print("discharging");
+ break;
+ case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+ pw.print("not-charging");
+ break;
+ case BatteryManager.BATTERY_STATUS_FULL:
+ pw.print("full");
+ break;
+ default:
+ pw.print(oldStatus);
+ break;
+ }
+ }
+ if (oldHealth != rec.batteryHealth) {
+ oldHealth = rec.batteryHealth;
+ pw.print(" health=");
+ switch (oldHealth) {
+ case BatteryManager.BATTERY_HEALTH_UNKNOWN:
+ pw.print("unknown");
+ break;
+ case BatteryManager.BATTERY_HEALTH_GOOD:
+ pw.print("good");
+ break;
+ case BatteryManager.BATTERY_HEALTH_OVERHEAT:
+ pw.print("overheat");
+ break;
+ case BatteryManager.BATTERY_HEALTH_DEAD:
+ pw.print("dead");
+ break;
+ case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
+ pw.print("over-voltage");
+ break;
+ case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
+ pw.print("failure");
+ break;
+ default:
+ pw.print(oldHealth);
+ break;
+ }
+ }
+ if (oldPlug != rec.batteryPlugType) {
+ oldPlug = rec.batteryPlugType;
+ pw.print(" plug=");
+ switch (oldPlug) {
+ case 0:
+ pw.print("none");
+ break;
+ case BatteryManager.BATTERY_PLUGGED_AC:
+ pw.print("ac");
+ break;
+ case BatteryManager.BATTERY_PLUGGED_USB:
+ pw.print("usb");
+ break;
+ default:
+ pw.print(oldPlug);
+ break;
+ }
+ }
+ if (oldTemp != rec.batteryTemperature) {
+ oldTemp = rec.batteryTemperature;
+ pw.print(" temp=");
+ pw.print(oldTemp);
+ }
+ if (oldVolt != rec.batteryVoltage) {
+ oldVolt = rec.batteryVoltage;
+ pw.print(" volt=");
+ pw.print(oldVolt);
+ }
+ printBitDescriptions(pw, oldState, rec.states,
+ HISTORY_STATE_DESCRIPTIONS);
+ pw.println();
+ }
+ oldState = rec.states;
+ }
+ }
+
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
@@ -1760,122 +2051,28 @@ public abstract class BatteryStats implements Parcelable {
*/
@SuppressWarnings("unused")
public void dumpLocked(PrintWriter pw) {
+ prepareForDumpLocked();
+
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
final HistoryItem rec = new HistoryItem();
if (startIteratingHistoryLocked()) {
pw.println("Battery History:");
- long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
- int oldState = 0;
- int oldStatus = -1;
- int oldHealth = -1;
- int oldPlug = -1;
- int oldTemp = -1;
- int oldVolt = -1;
+ HistoryPrinter hprinter = new HistoryPrinter();
while (getNextHistoryLocked(rec)) {
- pw.print(" ");
- TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
- pw.print(" ");
- if (rec.cmd == HistoryItem.CMD_START) {
- pw.println(" START");
- } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
- pw.println(" *OVERFLOW*");
- } else {
- if (rec.batteryLevel < 10) pw.print("00");
- else if (rec.batteryLevel < 100) pw.print("0");
- pw.print(rec.batteryLevel);
- pw.print(" ");
- if (rec.states < 0x10) pw.print("0000000");
- else if (rec.states < 0x100) pw.print("000000");
- else if (rec.states < 0x1000) pw.print("00000");
- else if (rec.states < 0x10000) pw.print("0000");
- else if (rec.states < 0x100000) pw.print("000");
- else if (rec.states < 0x1000000) pw.print("00");
- else if (rec.states < 0x10000000) pw.print("0");
- pw.print(Integer.toHexString(rec.states));
- if (oldStatus != rec.batteryStatus) {
- oldStatus = rec.batteryStatus;
- pw.print(" status=");
- switch (oldStatus) {
- case BatteryManager.BATTERY_STATUS_UNKNOWN:
- pw.print("unknown");
- break;
- case BatteryManager.BATTERY_STATUS_CHARGING:
- pw.print("charging");
- break;
- case BatteryManager.BATTERY_STATUS_DISCHARGING:
- pw.print("discharging");
- break;
- case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
- pw.print("not-charging");
- break;
- case BatteryManager.BATTERY_STATUS_FULL:
- pw.print("full");
- break;
- default:
- pw.print(oldStatus);
- break;
- }
- }
- if (oldHealth != rec.batteryHealth) {
- oldHealth = rec.batteryHealth;
- pw.print(" health=");
- switch (oldHealth) {
- case BatteryManager.BATTERY_HEALTH_UNKNOWN:
- pw.print("unknown");
- break;
- case BatteryManager.BATTERY_HEALTH_GOOD:
- pw.print("good");
- break;
- case BatteryManager.BATTERY_HEALTH_OVERHEAT:
- pw.print("overheat");
- break;
- case BatteryManager.BATTERY_HEALTH_DEAD:
- pw.print("dead");
- break;
- case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
- pw.print("over-voltage");
- break;
- case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
- pw.print("failure");
- break;
- default:
- pw.print(oldHealth);
- break;
- }
- }
- if (oldPlug != rec.batteryPlugType) {
- oldPlug = rec.batteryPlugType;
- pw.print(" plug=");
- switch (oldPlug) {
- case 0:
- pw.print("none");
- break;
- case BatteryManager.BATTERY_PLUGGED_AC:
- pw.print("ac");
- break;
- case BatteryManager.BATTERY_PLUGGED_USB:
- pw.print("usb");
- break;
- default:
- pw.print(oldPlug);
- break;
- }
- }
- if (oldTemp != rec.batteryTemperature) {
- oldTemp = rec.batteryTemperature;
- pw.print(" temp=");
- pw.print(oldTemp);
- }
- if (oldVolt != rec.batteryVoltage) {
- oldVolt = rec.batteryVoltage;
- pw.print(" volt=");
- pw.print(oldVolt);
- }
- printBitDescriptions(pw, oldState, rec.states,
- HISTORY_STATE_DESCRIPTIONS);
- pw.println();
- }
- oldState = rec.states;
+ hprinter.printNextItem(pw, rec, now);
+ }
+ finishIteratingHistoryLocked();
+ pw.println("");
+ }
+
+ if (startIteratingOldHistoryLocked()) {
+ pw.println("Old battery History:");
+ HistoryPrinter hprinter = new HistoryPrinter();
+ while (getNextOldHistoryLocked(rec)) {
+ hprinter.printNextItem(pw, rec, now);
}
+ finishIteratingOldHistoryLocked();
pw.println("");
}
@@ -1917,6 +2114,8 @@ public abstract class BatteryStats implements Parcelable {
@SuppressWarnings("unused")
public void dumpCheckinLocked(PrintWriter pw, String[] args, List<ApplicationInfo> apps) {
+ prepareForDumpLocked();
+
boolean isUnpluggedOnly = false;
for (String arg : args) {
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ae1e1c2..c25ebb7 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -16,7 +16,6 @@
package android.os;
-import android.util.Config;
import android.util.Log;
import java.io.FileDescriptor;
@@ -291,7 +290,7 @@ public class Binder implements IBinder {
*/
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
- if (Config.LOGV) Log.v("Binder", "Transact: " + code + " to " + this);
+ if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
@@ -413,7 +412,7 @@ final class BinderProxy implements IBinder {
private native final void destroy();
private static final void sendDeathNotice(DeathRecipient recipient) {
- if (Config.LOGV) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+ if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
try {
recipient.binderDied();
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 6257e95..8ff5beb 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -245,6 +245,11 @@ public class Build {
* switch them in to screen size compatibility mode.</p>
*/
public static final int HONEYCOMB_MR2 = 13;
+
+ /**
+ * Current version under development.
+ */
+ public static final int ICE_CREAM_SANDWICH = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 87aeccb..ba69246 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -18,7 +18,6 @@ package android.os;
import com.android.internal.util.TypedProperties;
-import android.util.Config;
import android.util.Log;
import java.io.FileDescriptor;
@@ -1031,7 +1030,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* Load the debug properties from the standard files into debugProperties.
*/
static {
- if (Config.DEBUG) {
+ if (false) {
final String TAG = "DebugProperties";
final String[] files = { "/system/debug.prop", "/debug.prop", "/data/debug.prop" };
final TypedProperties tp = new TypedProperties();
@@ -1157,10 +1156,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Reflectively sets static fields of a class based on internal debugging
- * properties. This method is a no-op if android.util.Config.DEBUG is
+ * properties. This method is a no-op if false is
* false.
* <p>
- * <strong>NOTE TO APPLICATION DEVELOPERS</strong>: Config.DEBUG will
+ * <strong>NOTE TO APPLICATION DEVELOPERS</strong>: false will
* always be false in release builds. This API is typically only useful
* for platform developers.
* </p>
@@ -1211,7 +1210,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* the internal debugging property value.
*/
public static void setFieldsOn(Class<?> cl, boolean partial) {
- if (Config.DEBUG) {
+ if (false) {
if (debugProperties != null) {
/* Only look for fields declared directly by the class,
* so we don't mysteriously change static fields in superclasses.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 1f3f6d9..11f9445 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -16,13 +16,13 @@
package android.os;
-import java.io.File;
-
import android.content.res.Resources;
import android.os.storage.IMountService;
import android.os.storage.StorageVolume;
import android.util.Log;
+import java.io.File;
+
/**
* Provides access to environment variables.
*/
@@ -113,18 +113,18 @@ public class Environment {
= getDirectory("ANDROID_SECURE_DATA", "/data/secure");
private static final File EXTERNAL_STORAGE_DIRECTORY
- = getDirectory("EXTERNAL_STORAGE", "/sdcard");
+ = getDirectory("EXTERNAL_STORAGE", "/mnt/sdcard");
private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY
- = new File (new File(getDirectory("EXTERNAL_STORAGE", "/sdcard"),
+ = new File (new File(getDirectory("EXTERNAL_STORAGE", "/mnt/sdcard"),
"Android"), "data");
private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY
- = new File (new File(getDirectory("EXTERNAL_STORAGE", "/sdcard"),
+ = new File (new File(getDirectory("EXTERNAL_STORAGE", "/mnt/sdcard"),
"Android"), "media");
private static final File EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY
- = new File (new File(getDirectory("EXTERNAL_STORAGE", "/sdcard"),
+ = new File (new File(getDirectory("EXTERNAL_STORAGE", "/mnt/sdcard"),
"Android"), "obb");
private static final File DOWNLOAD_CACHE_DIRECTORY
@@ -357,54 +357,54 @@ public class Environment {
}
/**
- * getExternalStorageState() returns MEDIA_REMOVED if the media is not present.
+ * {@link #getExternalStorageState()} returns MEDIA_REMOVED if the media is not present.
*/
public static final String MEDIA_REMOVED = "removed";
/**
- * getExternalStorageState() returns MEDIA_UNMOUNTED if the media is present
+ * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTED if the media is present
* but not mounted.
*/
public static final String MEDIA_UNMOUNTED = "unmounted";
/**
- * getExternalStorageState() returns MEDIA_CHECKING if the media is present
+ * {@link #getExternalStorageState()} returns MEDIA_CHECKING if the media is present
* and being disk-checked
*/
public static final String MEDIA_CHECKING = "checking";
/**
- * getExternalStorageState() returns MEDIA_NOFS if the media is present
+ * {@link #getExternalStorageState()} returns MEDIA_NOFS if the media is present
* but is blank or is using an unsupported filesystem
*/
public static final String MEDIA_NOFS = "nofs";
/**
- * getExternalStorageState() returns MEDIA_MOUNTED if the media is present
+ * {@link #getExternalStorageState()} returns MEDIA_MOUNTED if the media is present
* and mounted at its mount point with read/write access.
*/
public static final String MEDIA_MOUNTED = "mounted";
/**
- * getExternalStorageState() returns MEDIA_MOUNTED_READ_ONLY if the media is present
+ * {@link #getExternalStorageState()} returns MEDIA_MOUNTED_READ_ONLY if the media is present
* and mounted at its mount point with read only access.
*/
public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
/**
- * getExternalStorageState() returns MEDIA_SHARED if the media is present
+ * {@link #getExternalStorageState()} returns MEDIA_SHARED if the media is present
* not mounted, and shared via USB mass storage.
*/
public static final String MEDIA_SHARED = "shared";
/**
- * getExternalStorageState() returns MEDIA_BAD_REMOVAL if the media was
+ * {@link #getExternalStorageState()} returns MEDIA_BAD_REMOVAL if the media was
* removed before it was unmounted.
*/
public static final String MEDIA_BAD_REMOVAL = "bad_removal";
/**
- * getExternalStorageState() returns MEDIA_UNMOUNTABLE if the media is present
+ * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTABLE if the media is present
* but cannot be mounted. Typically this happens if the file system on the
* media is corrupted.
*/
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 632daa1..215e836 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -21,6 +21,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Pattern;
@@ -228,6 +229,22 @@ public class FileUtils {
}
}
+ /**
+ * Writes string to file. Basically same as "echo -n $string > $filename"
+ *
+ * @param filename
+ * @param string
+ * @throws IOException
+ */
+ public static void stringToFile(String filename, String string) throws IOException {
+ FileWriter out = new FileWriter(filename);
+ try {
+ out.write(string);
+ } finally {
+ out.close();
+ }
+ }
+
/**
* Computes the checksum of a file using the CRC32 checksum routine.
* The value of the checksum is returned.
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 5a245f8..f17a6f2 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -19,6 +19,7 @@ package android.os;
import android.net.InterfaceConfiguration;
import android.net.INetworkManagementEventObserver;
+import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.wifi.WifiConfiguration;
@@ -82,7 +83,6 @@ interface INetworkManagementService
** TETHERING RELATED
**/
-
/**
* Returns true if IP forwarding is enabled
*/
@@ -198,17 +198,29 @@ interface INetworkManagementService
void setAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface);
/**
- * Read number of bytes sent over an interface
+ ** DATA USAGE RELATED
+ **/
+
+ /**
+ * Return global network statistics summarized at an interface level,
+ * without any UID-level granularity.
+ */
+ NetworkStats getNetworkStatsSummary();
+
+ /**
+ * Return detailed network statistics with UID-level granularity,
+ * including interface and tag details.
*/
- long getInterfaceTxCounter(String iface);
+ NetworkStats getNetworkStatsDetail();
/**
- * Read number of bytes received over an interface
+ * Return detailed network statistics for the requested UID,
+ * including interface and tag details.
*/
- long getInterfaceRxCounter(String iface);
+ NetworkStats getNetworkStatsUidDetail(int uid);
/**
- * Configures bandwidth throttling on an interface
+ * Configures bandwidth throttling on an interface.
*/
void setInterfaceThrottle(String iface, int rxKbps, int txKbps);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index ccf642c..3edd692 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -16,7 +16,6 @@
package android.os;
-import android.util.Config;
import android.util.Log;
import android.util.Printer;
import android.util.PrefixPrinter;
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index f82702a..e8148f7 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -28,7 +28,7 @@ import java.io.OutputStream;
* MemoryFile is a wrapper for the Linux ashmem driver.
* MemoryFiles are backed by shared memory, which can be optionally
* set to be purgeable.
- * Purgeable files may have their contents reclaimed by the kernel
+ * Purgeable files may have their contents reclaimed by the kernel
* in low memory conditions (only if allowPurging is set to true).
* After a file is purged, attempts to read or write the file will
* cause an IOException to be thrown.
@@ -126,7 +126,7 @@ public class MemoryFile
close();
}
}
-
+
/**
* Returns the length of the memory file.
*
@@ -190,7 +190,7 @@ public class MemoryFile
* @return number of bytes read.
* @throws IOException if the memory file has been purged or deactivated.
*/
- public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
+ public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
if (isDeactivated()) {
throw new IOException("Can't read from deactivated memory file.");
@@ -330,6 +330,7 @@ public class MemoryFile
@Override
public void write(byte buffer[], int offset, int count) throws IOException {
writeBytes(buffer, offset, mOffset, count);
+ mOffset += count;
}
@Override
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index bb07825..a658fc4 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -17,7 +17,6 @@
package android.os;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
@@ -128,7 +127,7 @@ public class MessageQueue {
mBlocked = false;
mMessages = msg.next;
msg.next = null;
- if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
+ if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
} else {
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 0f1354b..3ea3f56 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -21,6 +21,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.net.DatagramSocket;
import java.net.Socket;
/**
@@ -35,64 +36,64 @@ public class ParcelFileDescriptor implements Parcelable {
//consider ParcelFileDescriptor A(fileDescriptor fd), ParcelFileDescriptor B(A)
//in this particular case fd.close might be invoked twice.
private final ParcelFileDescriptor mParcelDescriptor;
-
+
/**
* For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
* and this file doesn't already exist, then create the file with
* permissions such that any application can read it.
*/
public static final int MODE_WORLD_READABLE = 0x00000001;
-
+
/**
* For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
* and this file doesn't already exist, then create the file with
* permissions such that any application can write it.
*/
public static final int MODE_WORLD_WRITEABLE = 0x00000002;
-
+
/**
* For use with {@link #open}: open the file with read-only access.
*/
public static final int MODE_READ_ONLY = 0x10000000;
-
+
/**
* For use with {@link #open}: open the file with write-only access.
*/
public static final int MODE_WRITE_ONLY = 0x20000000;
-
+
/**
* For use with {@link #open}: open the file with read and write access.
*/
public static final int MODE_READ_WRITE = 0x30000000;
-
+
/**
* For use with {@link #open}: create the file if it doesn't already exist.
*/
public static final int MODE_CREATE = 0x08000000;
-
+
/**
* For use with {@link #open}: erase contents of file when opening.
*/
public static final int MODE_TRUNCATE = 0x04000000;
-
+
/**
* For use with {@link #open}: append to end of file while writing.
*/
public static final int MODE_APPEND = 0x02000000;
-
+
/**
* Create a new ParcelFileDescriptor accessing a given file.
- *
+ *
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
* {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or
* {@link #MODE_READ_WRITE}; may also be any combination of
* {@link #MODE_CREATE}, {@link #MODE_TRUNCATE},
* {@link #MODE_WORLD_READABLE}, and {@link #MODE_WORLD_WRITEABLE}.
- *
+ *
* @return Returns a new ParcelFileDescriptor pointing to the given
* file.
- *
+ *
* @throws FileNotFoundException Throws FileNotFoundException if the given
* file does not exist or can not be opened with the requested mode.
*/
@@ -106,12 +107,12 @@ public class ParcelFileDescriptor implements Parcelable {
security.checkWrite(path);
}
}
-
+
if ((mode&MODE_READ_WRITE) == 0) {
throw new IllegalArgumentException(
"Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
}
-
+
FileDescriptor fd = Parcel.openFileDescriptor(path, mode);
return fd != null ? new ParcelFileDescriptor(fd) : null;
}
@@ -176,12 +177,23 @@ public class ParcelFileDescriptor implements Parcelable {
* specified Socket.
*/
public static ParcelFileDescriptor fromSocket(Socket socket) {
- FileDescriptor fd = getFileDescriptorFromSocket(socket);
+ FileDescriptor fd = socket.getFileDescriptor$();
return fd != null ? new ParcelFileDescriptor(fd) : null;
}
- // Extracts the file descriptor from the specified socket and returns it untouched
- private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
+ /**
+ * Create a new ParcelFileDescriptor from the specified DatagramSocket.
+ *
+ * @param datagramSocket The DatagramSocket whose FileDescriptor is used
+ * to create a new ParcelFileDescriptor.
+ *
+ * @return A new ParcelFileDescriptor with the FileDescriptor of the
+ * specified DatagramSocket.
+ */
+ public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) {
+ FileDescriptor fd = datagramSocket.getFileDescriptor$();
+ return fd != null ? new ParcelFileDescriptor(fd) : null;
+ }
/**
* Create two ParcelFileDescriptors structured as a data pipe. The first
@@ -223,26 +235,26 @@ public class ParcelFileDescriptor implements Parcelable {
/**
* Retrieve the actual FileDescriptor associated with this object.
- *
+ *
* @return Returns the FileDescriptor associated with this object.
*/
public FileDescriptor getFileDescriptor() {
return mFileDescriptor;
}
-
+
/**
* Return the total size of the file representing this fd, as determined
* by stat(). Returns -1 if the fd is not a file.
*/
public native long getStatSize();
-
+
/**
* This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream,
* and I really don't think we want it to be public.
* @hide
*/
public native long seekTo(long pos);
-
+
/**
* Return the native fd int for this ParcelFileDescriptor. The
* ParcelFileDescriptor still owns the fd, and it still must be closed
@@ -254,9 +266,9 @@ public class ParcelFileDescriptor implements Parcelable {
}
return getFdNative();
}
-
+
private native int getFdNative();
-
+
/**
* Return the native fd int for this ParcelFileDescriptor and detach it
* from the object here. You are now responsible for closing the fd in
@@ -276,11 +288,11 @@ public class ParcelFileDescriptor implements Parcelable {
Parcel.clearFileDescriptor(mFileDescriptor);
return fd;
}
-
+
/**
* Close the ParcelFileDescriptor. This implementation closes the underlying
* OS resources allocated to represent this stream.
- *
+ *
* @throws IOException
* If an error occurs attempting to close this ParcelFileDescriptor.
*/
@@ -297,7 +309,7 @@ public class ParcelFileDescriptor implements Parcelable {
Parcel.closeFileDescriptor(mFileDescriptor);
}
}
-
+
/**
* An InputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
@@ -305,7 +317,7 @@ public class ParcelFileDescriptor implements Parcelable {
*/
public static class AutoCloseInputStream extends FileInputStream {
private final ParcelFileDescriptor mFd;
-
+
public AutoCloseInputStream(ParcelFileDescriptor fd) {
super(fd.getFileDescriptor());
mFd = fd;
@@ -320,7 +332,7 @@ public class ParcelFileDescriptor implements Parcelable {
}
}
}
-
+
/**
* An OutputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
@@ -328,7 +340,7 @@ public class ParcelFileDescriptor implements Parcelable {
*/
public static class AutoCloseOutputStream extends FileOutputStream {
private final ParcelFileDescriptor mFd;
-
+
public AutoCloseOutputStream(ParcelFileDescriptor fd) {
super(fd.getFileDescriptor());
mFd = fd;
@@ -343,12 +355,12 @@ public class ParcelFileDescriptor implements Parcelable {
}
}
}
-
+
@Override
public String toString() {
return "{ParcelFileDescriptor: " + mFileDescriptor + "}";
}
-
+
@Override
protected void finalize() throws Throwable {
try {
@@ -359,13 +371,13 @@ public class ParcelFileDescriptor implements Parcelable {
super.finalize();
}
}
-
+
public ParcelFileDescriptor(ParcelFileDescriptor descriptor) {
super();
mParcelDescriptor = descriptor;
mFileDescriptor = mParcelDescriptor.mFileDescriptor;
}
-
+
/*package */ParcelFileDescriptor(FileDescriptor descriptor) {
super();
if (descriptor == null) {
@@ -374,7 +386,7 @@ public class ParcelFileDescriptor implements Parcelable {
mFileDescriptor = descriptor;
mParcelDescriptor = null;
}
-
+
/* Parcelable interface */
public int describeContents() {
return Parcelable.CONTENTS_FILE_DESCRIPTOR;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 78275a4..a17983a 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -97,7 +97,8 @@ import android.util.Log;
* </tbody>
* </table>
*
- *
+ * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
+ * permission in an {@code &lt;uses-permission&gt;} element of the application's manifest.
*/
public class PowerManager
{
@@ -199,8 +200,11 @@ public class PowerManager
/**
* Class lets you say that you need to have the device on.
- *
- * <p>Call release when you are done and don't need the lock anymore.
+ * <p>
+ * Call release when you are done and don't need the lock anymore.
+ * <p>
+ * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
+ * permission in an {@code &lt;uses-permission&gt;} element of the application's manifest.
*/
public class WakeLock
{
@@ -257,16 +261,10 @@ public class PowerManager
public void acquire()
{
synchronized (mToken) {
- if (!mRefCounted || mCount++ == 0) {
- try {
- mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);
- } catch (RemoteException e) {
- }
- mHeld = true;
- }
+ acquireLocked();
}
}
-
+
/**
* Makes sure the device is on at the level you asked when you created
* the wake lock. The lock will be released after the given timeout.
@@ -274,10 +272,22 @@ public class PowerManager
* @param timeout Release the lock after the give timeout in milliseconds.
*/
public void acquire(long timeout) {
- acquire();
- mHandler.postDelayed(mReleaser, timeout);
+ synchronized (mToken) {
+ acquireLocked();
+ mHandler.postDelayed(mReleaser, timeout);
+ }
}
+ private void acquireLocked() {
+ if (!mRefCounted || mCount++ == 0) {
+ mHandler.removeCallbacks(mReleaser);
+ try {
+ mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);
+ } catch (RemoteException e) {
+ }
+ mHeld = true;
+ }
+ }
/**
* Release your claim to the CPU or screen being on.
@@ -286,8 +296,7 @@ public class PowerManager
* It may turn off shortly after you release it, or it may not if there
* are other wake locks held.
*/
- public void release()
- {
+ public void release() {
release(0);
}
@@ -302,9 +311,9 @@ public class PowerManager
*
* {@hide}
*/
- public void release(int flags)
- {
+ public void release(int flags) {
synchronized (mToken) {
+ mHandler.removeCallbacks(mReleaser);
if (!mRefCounted || --mCount == 0) {
try {
mService.releaseWakeLock(mToken, flags);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 2bfada0..d475f36 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -92,6 +92,12 @@ public class Process {
public static final int SDCARD_RW_GID = 1015;
/**
+ * Defines the UID for the KeyChain service.
+ * @hide
+ */
+ public static final int KEYCHAIN_UID = 1020;
+
+ /**
* Defines the UID/GID for the NFC service process.
* @hide
*/
@@ -628,6 +634,20 @@ public class Process {
}
/**
+ * Returns the parent process id for a currently running process.
+ * @param pid the process id
+ * @return the parent process id of the process, or -1 if the process is not running.
+ * @hide
+ */
+ public static final int getParentPid(int pid) {
+ String[] procStatusLabels = { "PPid:" };
+ long[] procStatusValues = new long[1];
+ procStatusValues[0] = -1;
+ Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues);
+ return (int) procStatusValues[0];
+ }
+
+ /**
* Set the priority of a thread, based on Linux priorities.
*
* @param tid The identifier of the thread/process to change.
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index c1dd911..ae605fb 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -70,7 +70,7 @@ public class RecoverySystem {
private static File RECOVERY_DIR = new File("/cache/recovery");
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
private static File LOG_FILE = new File(RECOVERY_DIR, "log");
- private static String LAST_LOG_FILENAME = "last_log";
+ private static String LAST_PREFIX = "last_";
// Length limits for reading files.
private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
@@ -415,10 +415,11 @@ public class RecoverySystem {
Log.e(TAG, "Error reading recovery log", e);
}
- // Delete everything in RECOVERY_DIR except LAST_LOG_FILENAME
+ // Delete everything in RECOVERY_DIR except those beginning
+ // with LAST_PREFIX
String[] names = RECOVERY_DIR.list();
for (int i = 0; names != null && i < names.length; i++) {
- if (names[i].equals(LAST_LOG_FILENAME)) continue;
+ if (names[i].startsWith(LAST_PREFIX)) continue;
File f = new File(RECOVERY_DIR, names[i]);
if (!f.delete()) {
Log.e(TAG, "Can't delete: " + f);
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 1375a29..01c640a 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -1481,6 +1481,13 @@ public final class StrictMode {
onVmPolicyViolation(message, originStack);
}
+ /**
+ * @hide
+ */
+ public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
+ onVmPolicyViolation(null, originStack);
+ }
+
// 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/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index bc4208a..792e4c1 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -32,6 +32,7 @@ public class StorageVolume implements Parcelable {
private final boolean mRemovable;
private final boolean mEmulated;
private final int mMtpReserveSpace;
+ private final boolean mAllowMassStorage;
private int mStorageId;
// StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
@@ -39,23 +40,25 @@ public class StorageVolume implements Parcelable {
// ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
- public StorageVolume(String path, String description,
- boolean removable, boolean emulated, int mtpReserveSpace) {
+ public StorageVolume(String path, String description, boolean removable,
+ boolean emulated, int mtpReserveSpace, boolean allowMassStorage) {
mPath = path;
mDescription = description;
mRemovable = removable;
mEmulated = emulated;
mMtpReserveSpace = mtpReserveSpace;
+ mAllowMassStorage = allowMassStorage;
}
// for parcelling only
- private StorageVolume(String path, String description,
- boolean removable, boolean emulated, int mtpReserveSpace, int storageId) {
+ private StorageVolume(String path, String description, boolean removable,
+ boolean emulated, int mtpReserveSpace, int storageId, boolean allowMassStorage) {
mPath = path;
mDescription = description;
mRemovable = removable;
mEmulated = emulated;
mMtpReserveSpace = mtpReserveSpace;
+ mAllowMassStorage = allowMassStorage;
mStorageId = storageId;
}
@@ -130,6 +133,15 @@ public class StorageVolume implements Parcelable {
return mMtpReserveSpace;
}
+ /**
+ * Returns true if this volume can be shared via USB mass storage.
+ *
+ * @return whether mass storage is allowed
+ */
+ public boolean allowMassStorage() {
+ return mAllowMassStorage;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof StorageVolume && mPath != null) {
@@ -158,9 +170,10 @@ public class StorageVolume implements Parcelable {
int emulated = in.readInt();
int storageId = in.readInt();
int mtpReserveSpace = in.readInt();
+ int allowMassStorage = in.readInt();
return new StorageVolume(path, description,
removable == 1, emulated == 1,
- mtpReserveSpace, storageId);
+ mtpReserveSpace, storageId, allowMassStorage == 1);
}
public StorageVolume[] newArray(int size) {
@@ -179,5 +192,6 @@ public class StorageVolume implements Parcelable {
parcel.writeInt(mEmulated ? 1 : 0);
parcel.writeInt(mStorageId);
parcel.writeInt(mMtpReserveSpace);
+ parcel.writeInt(mAllowMassStorage ? 1 : 0);
}
}
diff --git a/core/java/android/pim/ICalendar.java b/core/java/android/pim/ICalendar.java
index 9c4eaf4..58c5c63 100644
--- a/core/java/android/pim/ICalendar.java
+++ b/core/java/android/pim/ICalendar.java
@@ -17,7 +17,6 @@
package android.pim;
import android.util.Log;
-import android.util.Config;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@@ -447,7 +446,7 @@ public class ICalendar {
component = current;
}
} catch (FormatException fe) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Cannot parse " + line, fe);
}
// for now, we ignore the parse error. Google Calendar seems
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index 282417d..fdd0783 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -21,7 +21,6 @@ import android.database.Cursor;
import android.provider.Calendar;
import android.text.TextUtils;
import android.text.format.Time;
-import android.util.Config;
import android.util.Log;
import java.util.List;
@@ -197,7 +196,7 @@ public class RecurrenceSet {
(TextUtils.isEmpty(duration))||
((TextUtils.isEmpty(rrule))&&
(TextUtils.isEmpty(rdate)))) {
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, "
+ "or RRULE/RDATE: "
+ component.toString());
@@ -211,7 +210,7 @@ public class RecurrenceSet {
long millis = start.toMillis(false /* use isDst */);
values.put(Calendar.Events.DTSTART, millis);
if (millis == -1) {
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "DTSTART is out of range: " + component.toString());
}
return false;
diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java
index 2bf6c7b..437e553 100644
--- a/core/java/android/preference/CheckBoxPreference.java
+++ b/core/java/android/preference/CheckBoxPreference.java
@@ -16,20 +16,11 @@
package android.preference;
-import android.app.Service;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.TypedArray;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.CheckBox;
import android.widget.Checkable;
-import android.widget.TextView;
/**
* A {@link Preference} that provides checkbox widget
@@ -41,31 +32,18 @@ import android.widget.TextView;
* @attr ref android.R.styleable#CheckBoxPreference_summaryOn
* @attr ref android.R.styleable#CheckBoxPreference_disableDependentsState
*/
-public class CheckBoxPreference extends Preference {
+public class CheckBoxPreference extends TwoStatePreference {
- private CharSequence mSummaryOn;
- private CharSequence mSummaryOff;
-
- private boolean mChecked;
- private boolean mSendAccessibilityEventViewClickedType;
-
- private AccessibilityManager mAccessibilityManager;
-
- private boolean mDisableDependentsState;
-
public CheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.CheckBoxPreference, defStyle, 0);
- mSummaryOn = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOn);
- mSummaryOff = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOff);
- mDisableDependentsState = a.getBoolean(
- com.android.internal.R.styleable.CheckBoxPreference_disableDependentsState, false);
+ setSummaryOn(a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOn));
+ setSummaryOff(a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOff));
+ setDisableDependentsState(a.getBoolean(
+ com.android.internal.R.styleable.CheckBoxPreference_disableDependentsState, false));
a.recycle();
-
- mAccessibilityManager =
- (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
}
public CheckBoxPreference(Context context, AttributeSet attrs) {
@@ -84,246 +62,9 @@ public class CheckBoxPreference extends Preference {
if (checkboxView != null && checkboxView instanceof Checkable) {
((Checkable) checkboxView).setChecked(mChecked);
- // send an event to announce the value change of the CheckBox and is done here
- // because clicking a preference does not immediately change the checked state
- // for example when enabling the WiFi
- if (mSendAccessibilityEventViewClickedType &&
- mAccessibilityManager.isEnabled() &&
- checkboxView.isEnabled()) {
- mSendAccessibilityEventViewClickedType = false;
-
- // we send an event on behalf of the check box because in onBind the latter
- // is detached from its parent and such views do not send accessibility events
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_VIEW_CLICKED);
- event.setClassName(checkboxView.getClass().getName());
- event.setPackageName(getContext().getPackageName());
- event.setEnabled(checkboxView.isEnabled());
- event.setContentDescription(checkboxView.getContentDescription());
- event.setChecked(((Checkable) checkboxView).isChecked());
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
- }
-
- // Sync the summary view
- TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary);
- if (summaryView != null) {
- boolean useDefaultSummary = true;
- if (mChecked && mSummaryOn != null) {
- summaryView.setText(mSummaryOn);
- useDefaultSummary = false;
- } else if (!mChecked && mSummaryOff != null) {
- summaryView.setText(mSummaryOff);
- useDefaultSummary = false;
- }
-
- if (useDefaultSummary) {
- final CharSequence summary = getSummary();
- if (summary != null) {
- summaryView.setText(summary);
- useDefaultSummary = false;
- }
- }
-
- int newVisibility = View.GONE;
- if (!useDefaultSummary) {
- // Someone has written to it
- newVisibility = View.VISIBLE;
- }
- if (newVisibility != summaryView.getVisibility()) {
- summaryView.setVisibility(newVisibility);
- }
- }
- }
-
- @Override
- protected void onClick() {
- super.onClick();
-
- boolean newValue = !isChecked();
-
- // in onBindView() an AccessibilityEventViewClickedType is sent to announce the change
- // not sending
- mSendAccessibilityEventViewClickedType = true;
-
- if (!callChangeListener(newValue)) {
- return;
- }
-
- setChecked(newValue);
- }
-
- /**
- * Sets the checked state and saves it to the {@link SharedPreferences}.
- *
- * @param checked The checked state.
- */
- public void setChecked(boolean checked) {
- if (mChecked != checked) {
- mChecked = checked;
- persistBoolean(checked);
- notifyDependencyChange(shouldDisableDependents());
- notifyChanged();
- }
- }
-
- /**
- * Returns the checked state.
- *
- * @return The checked state.
- */
- public boolean isChecked() {
- return mChecked;
- }
-
- @Override
- public boolean shouldDisableDependents() {
- boolean shouldDisable = mDisableDependentsState ? mChecked : !mChecked;
- return shouldDisable || super.shouldDisableDependents();
- }
-
- /**
- * Sets the summary to be shown when checked.
- *
- * @param summary The summary to be shown when checked.
- */
- public void setSummaryOn(CharSequence summary) {
- mSummaryOn = summary;
- if (isChecked()) {
- notifyChanged();
- }
- }
-
- /**
- * @see #setSummaryOn(CharSequence)
- * @param summaryResId The summary as a resource.
- */
- public void setSummaryOn(int summaryResId) {
- setSummaryOn(getContext().getString(summaryResId));
- }
-
- /**
- * Returns the summary to be shown when checked.
- * @return The summary.
- */
- public CharSequence getSummaryOn() {
- return mSummaryOn;
- }
-
- /**
- * Sets the summary to be shown when unchecked.
- *
- * @param summary The summary to be shown when unchecked.
- */
- public void setSummaryOff(CharSequence summary) {
- mSummaryOff = summary;
- if (!isChecked()) {
- notifyChanged();
- }
- }
-
- /**
- * @see #setSummaryOff(CharSequence)
- * @param summaryResId The summary as a resource.
- */
- public void setSummaryOff(int summaryResId) {
- setSummaryOff(getContext().getString(summaryResId));
- }
-
- /**
- * Returns the summary to be shown when unchecked.
- * @return The summary.
- */
- public CharSequence getSummaryOff() {
- return mSummaryOff;
- }
-
- /**
- * Returns whether dependents are disabled when this preference is on ({@code true})
- * or when this preference is off ({@code false}).
- *
- * @return Whether dependents are disabled when this preference is on ({@code true})
- * or when this preference is off ({@code false}).
- */
- public boolean getDisableDependentsState() {
- return mDisableDependentsState;
- }
-
- /**
- * Sets whether dependents are disabled when this preference is on ({@code true})
- * or when this preference is off ({@code false}).
- *
- * @param disableDependentsState The preference state that should disable dependents.
- */
- public void setDisableDependentsState(boolean disableDependentsState) {
- mDisableDependentsState = disableDependentsState;
- }
-
- @Override
- protected Object onGetDefaultValue(TypedArray a, int index) {
- return a.getBoolean(index, false);
- }
-
- @Override
- protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
- setChecked(restoreValue ? getPersistedBoolean(mChecked)
- : (Boolean) defaultValue);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- if (isPersistent()) {
- // No need to save instance state since it's persistent
- return superState;
- }
-
- final SavedState myState = new SavedState(superState);
- myState.checked = isChecked();
- return myState;
- }
-
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- if (state == null || !state.getClass().equals(SavedState.class)) {
- // Didn't save state for us in onSaveInstanceState
- super.onRestoreInstanceState(state);
- return;
- }
-
- SavedState myState = (SavedState) state;
- super.onRestoreInstanceState(myState.getSuperState());
- setChecked(myState.checked);
- }
-
- private static class SavedState extends BaseSavedState {
- boolean checked;
-
- public SavedState(Parcel source) {
- super(source);
- checked = source.readInt() == 1;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(checked ? 1 : 0);
+ sendAccessibilityEventForView(checkboxView);
}
- public SavedState(Parcelable superState) {
- super(superState);
- }
-
- 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];
- }
- };
+ syncSummaryView(view);
}
-
}
diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java
index 42d555c..2e8d551 100644
--- a/core/java/android/preference/MultiSelectListPreference.java
+++ b/core/java/android/preference/MultiSelectListPreference.java
@@ -169,9 +169,9 @@ public class MultiSelectListPreference extends DialogPreference {
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (isChecked) {
- mPreferenceChanged |= mNewValues.add(mEntries[which].toString());
+ mPreferenceChanged |= mNewValues.add(mEntryValues[which].toString());
} else {
- mPreferenceChanged |= mNewValues.remove(mEntries[which].toString());
+ mPreferenceChanged |= mNewValues.remove(mEntryValues[which].toString());
}
}
});
@@ -180,7 +180,7 @@ public class MultiSelectListPreference extends DialogPreference {
}
private boolean[] getSelectedItems() {
- final CharSequence[] entries = mEntries;
+ final CharSequence[] entries = mEntryValues;
final int entryCount = entries.length;
final Set<String> values = mValues;
boolean[] result = new boolean[entryCount];
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 93114ad..b6d1594 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -89,6 +89,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
private int mOrder = DEFAULT_ORDER;
private CharSequence mTitle;
+ private int mTitleRes;
private CharSequence mSummary;
/**
* mIconResId is overridden by mIcon, if mIcon is specified.
@@ -214,6 +215,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
break;
case com.android.internal.R.styleable.Preference_title:
+ mTitleRes = a.getResourceId(attr, 0);
mTitle = a.getString(attr);
break;
@@ -514,7 +516,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
}
}
-
+
ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
if (imageView != null) {
if (mIconResId != 0 || mIcon != null) {
@@ -590,6 +592,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
*/
public void setTitle(CharSequence title) {
if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
+ mTitleRes = 0;
mTitle = title;
notifyChanged();
}
@@ -603,9 +606,21 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
*/
public void setTitle(int titleResId) {
setTitle(mContext.getString(titleResId));
+ mTitleRes = titleResId;
}
/**
+ * Returns the title resource ID of this Preference. If the title did
+ * not come from a resource, 0 is returned.
+ *
+ * @return The title resource.
+ * @see #setTitle(int)
+ */
+ public int getTitleRes() {
+ return mTitleRes;
+ }
+
+ /**
* Returns the title of this Preference.
*
* @return The title.
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 8535bd4..14e7bed 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -132,13 +132,28 @@ public abstract class PreferenceActivity extends ListActivity implements
/**
* When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
- * this extra can also be specify to supply a Bundle of arguments to pass
+ * this extra can also be specified to supply a Bundle of arguments to pass
* to that fragment when it is instantiated during the initial creation
* of PreferenceActivity.
*/
public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
/**
+ * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
+ * this extra can also be specify to supply the title to be shown for
+ * that fragment.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
+
+ /**
+ * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
+ * this extra can also be specify to supply the short title to be shown for
+ * that fragment.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
+ = ":android:show_fragment_short_title";
+
+ /**
* When starting this activity, the invoking Intent can contain this extra
* boolean that the header list should not be displayed. This is most often
* used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
@@ -496,6 +511,8 @@ public abstract class PreferenceActivity extends ListActivity implements
mSinglePane = hidingHeaders || !onIsMultiPane();
String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
+ int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
if (savedInstanceState != null) {
// We are restarting from a previous saved state; used that to
@@ -516,6 +533,12 @@ public abstract class PreferenceActivity extends ListActivity implements
// new fragment mode, but don't need to compute and show
// the headers.
switchToHeader(initialFragment, initialArguments);
+ if (initialTitle != 0) {
+ CharSequence initialTitleStr = getText(initialTitle);
+ CharSequence initialShortTitleStr = initialShortTitle != 0
+ ? getText(initialShortTitle) : null;
+ showBreadCrumbs(initialTitleStr, initialShortTitleStr);
+ }
} else {
// We need to try to build the headers.
@@ -934,7 +957,8 @@ public abstract class PreferenceActivity extends ListActivity implements
/**
* Called when the user selects an item in the header list. The default
- * implementation will call either {@link #startWithFragment(String, Bundle, Fragment, int)}
+ * implementation will call either
+ * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
* or {@link #switchToHeader(Header)} as appropriate.
*
* @param header The header that was selected.
@@ -943,7 +967,14 @@ public abstract class PreferenceActivity extends ListActivity implements
public void onHeaderClick(Header header, int position) {
if (header.fragment != null) {
if (mSinglePane) {
- startWithFragment(header.fragment, header.fragmentArguments, null, 0);
+ int titleRes = header.breadCrumbTitleRes;
+ int shortTitleRes = header.breadCrumbShortTitleRes;
+ if (titleRes == 0) {
+ titleRes = header.titleRes;
+ shortTitleRes = 0;
+ }
+ startWithFragment(header.fragment, header.fragmentArguments, null, 0,
+ titleRes, shortTitleRes);
} else {
switchToHeader(header);
}
@@ -953,6 +984,41 @@ public abstract class PreferenceActivity extends ListActivity implements
}
/**
+ * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
+ * in single-pane mode, to build an Intent to launch a new activity showing
+ * the selected fragment. The default implementation constructs an Intent
+ * that re-launches the current activity with the appropriate arguments to
+ * display the fragment.
+ *
+ * @param fragmentName The name of the fragment to display.
+ * @param args Optional arguments to supply to the fragment.
+ * @param titleRes Optional resource ID of title to show for this item.
+ * @param titleRes Optional resource ID of short title to show for this item.
+ * @return Returns an Intent that can be launched to display the given
+ * fragment.
+ */
+ public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
+ int titleRes, int shortTitleRes) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(this, getClass());
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
+ intent.putExtra(EXTRA_NO_HEADERS, true);
+ return intent;
+ }
+
+ /**
+ * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
+ * but uses a 0 titleRes.
+ */
+ public void startWithFragment(String fragmentName, Bundle args,
+ Fragment resultTo, int resultRequestCode) {
+ startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
+ }
+
+ /**
* Start a new instance of this activity, showing only the given
* preference fragment. When launched in this mode, the header list
* will be hidden and the given preference fragment will be instantiated
@@ -960,14 +1026,18 @@ public abstract class PreferenceActivity extends ListActivity implements
*
* @param fragmentName The name of the fragment to display.
* @param args Optional arguments to supply to the fragment.
+ * @param resultTo Option fragment that should receive the result of
+ * the activity launch.
+ * @param resultRequestCode If resultTo is non-null, this is the request
+ * code in which to report the result.
+ * @param titleRes Resource ID of string to display for the title of
+ * this set of preferences.
+ * @param titleRes Resource ID of string to display for the short title of
+ * this set of preferences.
*/
public void startWithFragment(String fragmentName, Bundle args,
- Fragment resultTo, int resultRequestCode) {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClass(this, getClass());
- intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
- intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
- intent.putExtra(EXTRA_NO_HEADERS, true);
+ Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
+ Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
if (resultTo == null) {
startActivity(intent);
} else {
@@ -984,16 +1054,16 @@ public abstract class PreferenceActivity extends ListActivity implements
if (mFragmentBreadCrumbs == null) {
View crumbs = findViewById(android.R.id.title);
// For screens with a different kind of title, don't create breadcrumbs.
- if (crumbs != null && !(crumbs instanceof FragmentBreadCrumbs)) return;
- mFragmentBreadCrumbs = (FragmentBreadCrumbs) findViewById(android.R.id.title);
+ try {
+ mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
+ } catch (ClassCastException e) {
+ return;
+ }
if (mFragmentBreadCrumbs == null) {
- mFragmentBreadCrumbs = new FragmentBreadCrumbs(this);
- ActionBar actionBar = getActionBar();
- if (actionBar != null) {
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
- actionBar.setCustomView(mFragmentBreadCrumbs);
+ if (title != null) {
+ setTitle(title);
}
+ return;
}
mFragmentBreadCrumbs.setMaxVisible(2);
mFragmentBreadCrumbs.setActivity(this);
@@ -1161,7 +1231,7 @@ public abstract class PreferenceActivity extends ListActivity implements
public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
CharSequence titleText, Fragment resultTo, int resultRequestCode) {
if (mSinglePane) {
- startWithFragment(fragmentClass, args, resultTo, resultRequestCode);
+ startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
} else {
Fragment f = Fragment.instantiate(this, fragmentClass, args);
if (resultTo != null) {
@@ -1207,7 +1277,8 @@ public abstract class PreferenceActivity extends ListActivity implements
@Override
public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
- startPreferencePanel(pref.getFragment(), pref.getExtras(), 0, pref.getTitle(), null, 0);
+ startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
+ pref.getTitle(), null, 0);
return true;
}
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 4e22ba0..9d46b7a 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -151,8 +152,8 @@ public abstract class PreferenceFragment extends Fragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- return inflater.inflate(com.android.internal.R.layout.preference_list_fragment,
- container, false);
+ return inflater.inflate(com.android.internal.R.layout.preference_list_fragment, container,
+ false);
}
@Override
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
new file mode 100644
index 0000000..f681526
--- /dev/null
+++ b/core/java/android/preference/SwitchPreference.java
@@ -0,0 +1,180 @@
+/*
+ * 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.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Checkable;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+/**
+ * A {@link Preference} that provides a two-state toggleable option.
+ * <p>
+ * This preference will store a boolean into the SharedPreferences.
+ *
+ * @attr ref android.R.styleable#SwitchPreference_summaryOff
+ * @attr ref android.R.styleable#SwitchPreference_summaryOn
+ * @attr ref android.R.styleable#SwitchPreference_switchTextOff
+ * @attr ref android.R.styleable#SwitchPreference_switchTextOn
+ * @attr ref android.R.styleable#SwitchPreference_disableDependentsState
+ */
+public class SwitchPreference extends TwoStatePreference {
+ // Switch text for on and off states
+ private CharSequence mSwitchOn;
+ private CharSequence mSwitchOff;
+ private final Listener mListener = new Listener();
+
+ private class Listener implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
+ @Override
+ public void onClick(View v) {
+ SwitchPreference.this.onClick();
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ SwitchPreference.this.setChecked(isChecked);
+ }
+ }
+
+ /**
+ * Construct a new SwitchPreference with the given style options.
+ *
+ * @param context The Context that will style this preference
+ * @param attrs Style attributes that differ from the default
+ * @param defStyle Theme attribute defining the default style options
+ */
+ public SwitchPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.SwitchPreference, defStyle, 0);
+ setSummaryOn(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOn));
+ setSummaryOff(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOff));
+ setSwitchTextOn(a.getString(
+ com.android.internal.R.styleable.SwitchPreference_switchTextOn));
+ setSwitchTextOff(a.getString(
+ com.android.internal.R.styleable.SwitchPreference_switchTextOff));
+ setDisableDependentsState(a.getBoolean(
+ com.android.internal.R.styleable.SwitchPreference_disableDependentsState, false));
+ a.recycle();
+ }
+
+ /**
+ * Construct a new SwitchPreference with the given style options.
+ *
+ * @param context The Context that will style this preference
+ * @param attrs Style attributes that differ from the default
+ */
+ public SwitchPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.switchPreferenceStyle);
+ }
+
+ /**
+ * Construct a new SwitchPreference with default style options.
+ *
+ * @param context The Context that will style this preference
+ */
+ public SwitchPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
+ if (checkableView != null && checkableView instanceof Checkable) {
+ ((Checkable) checkableView).setChecked(mChecked);
+
+ sendAccessibilityEventForView(checkableView);
+
+ if (checkableView instanceof Switch) {
+ final Switch switchView = (Switch) checkableView;
+ switchView.setTextOn(mSwitchOn);
+ switchView.setTextOff(mSwitchOff);
+ switchView.setOnCheckedChangeListener(mListener);
+ }
+
+ if (checkableView.hasFocusable()) {
+ // This is a focusable list item. Attach a click handler to toggle the button
+ // for the rest of the item.
+ view.setOnClickListener(mListener);
+ }
+ }
+
+ syncSummaryView(view);
+ }
+
+ /**
+ * Set the text displayed on the switch widget in the on state.
+ * This should be a very short string; one word if possible.
+ *
+ * @param onText Text to display in the on state
+ */
+ public void setSwitchTextOn(CharSequence onText) {
+ mSwitchOn = onText;
+ notifyChanged();
+ }
+
+ /**
+ * Set the text displayed on the switch widget in the off state.
+ * This should be a very short string; one word if possible.
+ *
+ * @param offText Text to display in the off state
+ */
+ public void setSwitchTextOff(CharSequence offText) {
+ mSwitchOff = offText;
+ notifyChanged();
+ }
+
+ /**
+ * Set the text displayed on the switch widget in the on state.
+ * This should be a very short string; one word if possible.
+ *
+ * @param resId The text as a string resource ID
+ */
+ public void setSwitchTextOn(int resId) {
+ setSwitchTextOn(getContext().getString(resId));
+ }
+
+ /**
+ * Set the text displayed on the switch widget in the off state.
+ * This should be a very short string; one word if possible.
+ *
+ * @param resId The text as a string resource ID
+ */
+ public void setSwitchTextOff(int resId) {
+ setSwitchTextOff(getContext().getString(resId));
+ }
+
+ /**
+ * @return The text that will be displayed on the switch widget in the on state
+ */
+ public CharSequence getSwitchTextOn() {
+ return mSwitchOn;
+ }
+
+ /**
+ * @return The text that will be displayed on the switch widget in the off state
+ */
+ public CharSequence getSwitchTextOff() {
+ return mSwitchOff;
+ }
+}
diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java
new file mode 100644
index 0000000..8e21c4c
--- /dev/null
+++ b/core/java/android/preference/TwoStatePreference.java
@@ -0,0 +1,309 @@
+/*
+ * 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.preference;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.TextView;
+
+/**
+ * Common base class for preferences that have two selectable states, persist a
+ * boolean value in SharedPreferences, and may have dependent preferences that are
+ * enabled/disabled based on the current state.
+ */
+public abstract class TwoStatePreference extends Preference {
+
+ private CharSequence mSummaryOn;
+ private CharSequence mSummaryOff;
+ boolean mChecked;
+ private boolean mSendAccessibilityEventViewClickedType;
+ private AccessibilityManager mAccessibilityManager;
+ private boolean mDisableDependentsState;
+
+ public TwoStatePreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
+ }
+
+ public TwoStatePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
+ }
+
+ public TwoStatePreference(Context context) {
+ super(context);
+
+ mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
+ }
+
+ @Override
+ protected void onClick() {
+ super.onClick();
+
+ boolean newValue = !isChecked();
+
+ // in onBindView() an AccessibilityEventViewClickedType is sent to announce the change
+ // not sending
+ mSendAccessibilityEventViewClickedType = true;
+
+ if (!callChangeListener(newValue)) {
+ return;
+ }
+
+ setChecked(newValue);
+ }
+
+ /**
+ * Sets the checked state and saves it to the {@link SharedPreferences}.
+ *
+ * @param checked The checked state.
+ */
+ public void setChecked(boolean checked) {
+ if (mChecked != checked) {
+ mChecked = checked;
+ persistBoolean(checked);
+ notifyDependencyChange(shouldDisableDependents());
+ notifyChanged();
+ }
+ }
+
+ /**
+ * Returns the checked state.
+ *
+ * @return The checked state.
+ */
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ @Override
+ public boolean shouldDisableDependents() {
+ boolean shouldDisable = mDisableDependentsState ? mChecked : !mChecked;
+ return shouldDisable || super.shouldDisableDependents();
+ }
+
+ /**
+ * Sets the summary to be shown when checked.
+ *
+ * @param summary The summary to be shown when checked.
+ */
+ public void setSummaryOn(CharSequence summary) {
+ mSummaryOn = summary;
+ if (isChecked()) {
+ notifyChanged();
+ }
+ }
+
+ /**
+ * @see #setSummaryOn(CharSequence)
+ * @param summaryResId The summary as a resource.
+ */
+ public void setSummaryOn(int summaryResId) {
+ setSummaryOn(getContext().getString(summaryResId));
+ }
+
+ /**
+ * Returns the summary to be shown when checked.
+ * @return The summary.
+ */
+ public CharSequence getSummaryOn() {
+ return mSummaryOn;
+ }
+
+ /**
+ * Sets the summary to be shown when unchecked.
+ *
+ * @param summary The summary to be shown when unchecked.
+ */
+ public void setSummaryOff(CharSequence summary) {
+ mSummaryOff = summary;
+ if (!isChecked()) {
+ notifyChanged();
+ }
+ }
+
+ /**
+ * @see #setSummaryOff(CharSequence)
+ * @param summaryResId The summary as a resource.
+ */
+ public void setSummaryOff(int summaryResId) {
+ setSummaryOff(getContext().getString(summaryResId));
+ }
+
+ /**
+ * Returns the summary to be shown when unchecked.
+ * @return The summary.
+ */
+ public CharSequence getSummaryOff() {
+ return mSummaryOff;
+ }
+
+ /**
+ * Returns whether dependents are disabled when this preference is on ({@code true})
+ * or when this preference is off ({@code false}).
+ *
+ * @return Whether dependents are disabled when this preference is on ({@code true})
+ * or when this preference is off ({@code false}).
+ */
+ public boolean getDisableDependentsState() {
+ return mDisableDependentsState;
+ }
+
+ /**
+ * Sets whether dependents are disabled when this preference is on ({@code true})
+ * or when this preference is off ({@code false}).
+ *
+ * @param disableDependentsState The preference state that should disable dependents.
+ */
+ public void setDisableDependentsState(boolean disableDependentsState) {
+ mDisableDependentsState = disableDependentsState;
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getBoolean(index, false);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setChecked(restoreValue ? getPersistedBoolean(mChecked)
+ : (Boolean) defaultValue);
+ }
+
+ /**
+ * Send an accessibility event for the given view if appropriate
+ * @param view View that should send the event
+ */
+ void sendAccessibilityEventForView(View view) {
+ // send an event to announce the value change of the state. It is done here
+ // because clicking a preference does not immediately change the checked state
+ // for example when enabling the WiFi
+ if (mSendAccessibilityEventViewClickedType &&
+ mAccessibilityManager.isEnabled() &&
+ view.isEnabled()) {
+ mSendAccessibilityEventViewClickedType = false;
+
+ int eventType = AccessibilityEvent.TYPE_VIEW_CLICKED;
+ view.sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
+ }
+ }
+
+ /**
+ * Sync a summary view contained within view's subhierarchy with the correct summary text.
+ * @param view View where a summary should be located
+ */
+ void syncSummaryView(View view) {
+ // Sync the summary view
+ TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary);
+ if (summaryView != null) {
+ boolean useDefaultSummary = true;
+ if (mChecked && mSummaryOn != null) {
+ summaryView.setText(mSummaryOn);
+ useDefaultSummary = false;
+ } else if (!mChecked && mSummaryOff != null) {
+ summaryView.setText(mSummaryOff);
+ useDefaultSummary = false;
+ }
+
+ if (useDefaultSummary) {
+ final CharSequence summary = getSummary();
+ if (summary != null) {
+ summaryView.setText(summary);
+ useDefaultSummary = false;
+ }
+ }
+
+ int newVisibility = View.GONE;
+ if (!useDefaultSummary) {
+ // Someone has written to it
+ newVisibility = View.VISIBLE;
+ }
+ if (newVisibility != summaryView.getVisibility()) {
+ summaryView.setVisibility(newVisibility);
+ }
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.checked = isChecked();
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ setChecked(myState.checked);
+ }
+
+ static class SavedState extends BaseSavedState {
+ boolean checked;
+
+ public SavedState(Parcel source) {
+ super(source);
+ checked = source.readInt() == 1;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(checked ? 1 : 0);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ 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];
+ }
+ };
+ }
+}
diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java
index 03bc41a..d678205 100644
--- a/core/java/android/provider/BrowserContract.java
+++ b/core/java/android/provider/BrowserContract.java
@@ -332,6 +332,13 @@ public class BrowserContract {
* <P>Type: TEXT</P>
*/
public static final String ACCOUNT_TYPE = "account_type";
+
+ /**
+ * The ID of the account's root folder. This will be the ID of the folder
+ * returned when querying {@link Bookmarks#CONTENT_URI_DEFAULT_FOLDER}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ROOT_ID = "root_id";
}
/**
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index de71763..bb6ed9c 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -16,6 +16,7 @@
package android.provider;
+
import android.accounts.Account;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -32,7 +33,6 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
-import android.pim.ICalendar;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Time;
@@ -44,24 +44,30 @@ import android.util.Log;
* @hide
*/
public final class Calendar {
-
- public static final String TAG = "Calendar";
+ private static final String TAG = "Calendar";
/**
- * Broadcast Action: An event reminder.
+ * Broadcast Action: This is the intent that gets fired when an alarm
+ * notification needs to be posted for a reminder.
*/
public static final String EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
/**
- * These are the symbolic names for the keys used in the extra data
- * passed in the intent for event reminders.
+ * Intent Extras key: The start time of an event or an instance of a
+ * recurring event. (milliseconds since epoch)
*/
public static final String EVENT_BEGIN_TIME = "beginTime";
+
+ /**
+ * Intent Extras key: The end time of an event or an instance of a recurring
+ * event. (milliseconds since epoch)
+ */
public static final String EVENT_END_TIME = "endTime";
/**
- * This must not be changed or horrible, unspeakable things could happen.
- * For instance, the Calendar app might break. Also, the db might not work.
+ * This authority is used for writing to or querying from the calendar
+ * provider. Note: This is set at first run and cannot be changed without
+ * breaking apps that access the provider.
*/
public static final String AUTHORITY = "com.android.calendar";
@@ -73,204 +79,293 @@ public final class Calendar {
/**
* An optional insert, update or delete URI parameter that allows the caller
- * to specify that it is a sync adapter. The default value is false. If true
- * the dirty flag is not automatically set and the "syncToNetwork" parameter
- * is set to false when calling
- * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ * to specify that it is a sync adapter. The default value is false. If set
+ * to true, the modified row is not marked as "dirty" (needs to be synced)
+ * and when the provider calls
+ * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}
+ * , the third parameter "syncToNetwork" is set to false. Furthermore, if
+ * set to true, the caller must also include
+ * {@link Calendars#ACCOUNT_NAME} and {@link Calendars#ACCOUNT_TYPE} as
+ * query parameters.
+ *
+ * @see Uri.Builder#appendQueryParameter(java.lang.String, java.lang.String)
*/
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
-
/**
- * Generic columns for use by sync adapters. The specific functions of
- * these columns are private to the sync adapter. Other clients of the API
- * should not attempt to either read or write this column.
+ * A special account type for calendars not associated with any account.
+ * Normally calendars that do not match an account on the device will be
+ * removed. Setting the account_type on a calendar to this will prevent it
+ * from being wiped if it does not match an existing account.
+ *
+ * @see SyncColumns#ACCOUNT_TYPE
*/
- protected interface BaseSyncColumns {
-
- /** Generic column for use by sync adapters. */
- public static final String SYNC1 = "sync1";
- /** Generic column for use by sync adapters. */
- public static final String SYNC2 = "sync2";
- /** Generic column for use by sync adapters. */
- public static final String SYNC3 = "sync3";
- /** Generic column for use by sync adapters. */
- public static final String SYNC4 = "sync4";
- /** Generic column for use by sync adapters. */
- public static final String SYNC5 = "sync5";
- }
+ public static final String ACCOUNT_TYPE_LOCAL = "LOCAL";
/**
- * Columns for Sync information used by Calendars and Events tables.
+ * Generic columns for use by sync adapters. The specific functions of these
+ * columns are private to the sync adapter. Other clients of the API should
+ * not attempt to either read or write this column. These columns are
+ * editable as part of the Calendars Uri, but can only be read if accessed
+ * through any other Uri.
*/
- public interface SyncColumns extends BaseSyncColumns {
+ protected interface CalendarSyncColumns {
+
+
/**
- * The account that was used to sync the entry to the device.
+ * Generic column for use by sync adapters. Column name.
* <P>Type: TEXT</P>
*/
- public static final String _SYNC_ACCOUNT = "_sync_account";
+ public static final String CAL_SYNC1 = "cal_sync1";
/**
- * The type of the account that was used to sync the entry to the device.
+ * Generic column for use by sync adapters. Column name.
* <P>Type: TEXT</P>
*/
- public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
+ public static final String CAL_SYNC2 = "cal_sync2";
/**
- * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
+ * Generic column for use by sync adapters. Column name.
* <P>Type: TEXT</P>
*/
- public static final String _SYNC_ID = "_sync_id";
+ public static final String CAL_SYNC3 = "cal_sync3";
/**
- * The last time, from the sync source's point of view, that this row has been synchronized.
- * <P>Type: INTEGER (long)</P>
+ * Generic column for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
*/
- public static final String _SYNC_TIME = "_sync_time";
+ public static final String CAL_SYNC4 = "cal_sync4";
/**
- * The version of the row, as assigned by the server.
+ * Generic column for use by sync adapters. Column name.
* <P>Type: TEXT</P>
*/
- public static final String _SYNC_VERSION = "_sync_version";
+ public static final String CAL_SYNC5 = "cal_sync5";
/**
- * For use by sync adapter at its discretion; not modified by CalendarProvider
- * Note that this column was formerly named _SYNC_LOCAL_ID. We are using it to avoid a
- * schema change.
- * TODO Replace this with something more general in the future.
- * <P>Type: INTEGER (long)</P>
+ * Generic column for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
*/
- public static final String _SYNC_DATA = "_sync_local_id";
+ public static final String CAL_SYNC6 = "cal_sync6";
/**
- * Used only in persistent providers, and only during merging.
- * <P>Type: INTEGER (long)</P>
+ * Generic column for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
*/
- public static final String _SYNC_MARK = "_sync_mark";
+ public static final String CAL_SYNC7 = "cal_sync7";
/**
- * Used to indicate that local, unsynced, changes are present.
- * <P>Type: INTEGER (long)</P>
+ * Generic column for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CAL_SYNC8 = "cal_sync8";
+
+ /**
+ * Generic column for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
*/
- public static final String _SYNC_DIRTY = "_sync_dirty";
+ public static final String CAL_SYNC9 = "cal_sync9";
+ /**
+ * Generic column for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CAL_SYNC10 = "cal_sync10";
}
/**
- * Columns from the Account information used by Calendars and Events tables.
+ * Columns for Sync information used by Calendars and Events tables. These
+ * have specific uses which are expected to be consistent by the app and
+ * sync adapter.
+ *
+ * @hide
*/
- public interface AccountColumns {
+ public interface SyncColumns extends CalendarSyncColumns {
/**
- * The name of the account instance to which this row belongs, which when paired with
- * {@link #ACCOUNT_TYPE} identifies a specific account.
+ * The account that was used to sync the entry to the device. If the
+ * account_type is not {@link #ACCOUNT_TYPE_LOCAL} then the name and
+ * type must match an account on the device or the calendar will be
+ * deleted.
* <P>Type: TEXT</P>
*/
public static final String ACCOUNT_NAME = "account_name";
/**
- * The type of account to which this row belongs, which when paired with
- * {@link #ACCOUNT_NAME} identifies a specific account.
+ * The type of the account that was used to sync the entry to the
+ * device. A type of {@link #ACCOUNT_TYPE_LOCAL} will keep this event
+ * form being deleted if there are no matching accounts on the device.
* <P>Type: TEXT</P>
*/
public static final String ACCOUNT_TYPE = "account_type";
+
+ /**
+ * The unique ID for a row assigned by the sync source. NULL if the row
+ * has never been synced. This is used as a reference id for exceptions
+ * along with {@link BaseColumns#_ID}.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ID = "_sync_id";
+
+ /**
+ * Used to indicate that local, unsynced, changes are present.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DIRTY = "dirty";
+
+ /**
+ * Whether the row has been deleted but not synced to the server. A
+ * deleted row should be ignored.
+ * <P>
+ * Type: INTEGER (boolean)
+ * </P>
+ */
+ public static final String DELETED = "deleted";
+
+ /**
+ * If set to 1 this causes events on this calendar to be duplicated with
+ * {@link EventsColumns#LAST_SYNCED} set to 1 whenever the event
+ * transitions from non-dirty to dirty. The duplicated event will not be
+ * expanded in the instances table and will only show up in sync adapter
+ * queries of the events table. It will also be deleted when the
+ * originating event has its dirty flag cleared by the sync adapter.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CAN_PARTIALLY_UPDATE = "canPartiallyUpdate";
}
/**
- * Columns from the Calendars table that other tables join into themselves.
+ * Columns specific to the Calendars Uri that other Uris can query.
*/
- public interface CalendarsColumns {
+ private interface CalendarsColumns {
/**
* The color of the calendar
* <P>Type: INTEGER (color value)</P>
*/
- public static final String COLOR = "color";
+ public static final String CALENDAR_COLOR = "calendar_color";
+
+ /**
+ * The display name of the calendar. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CALENDAR_DISPLAY_NAME = "calendar_displayName";
/**
* The level of access that the user has for the calendar
* <P>Type: INTEGER (one of the values below)</P>
*/
- public static final String ACCESS_LEVEL = "access_level";
+ public static final String CALENDAR_ACCESS_LEVEL = "calendar_access_level";
/** Cannot access the calendar */
- public static final int NO_ACCESS = 0;
+ public static final int CAL_ACCESS_NONE = 0;
/** Can only see free/busy information about the calendar */
- public static final int FREEBUSY_ACCESS = 100;
+ public static final int CAL_ACCESS_FREEBUSY = 100;
/** Can read all event details */
- public static final int READ_ACCESS = 200;
- public static final int RESPOND_ACCESS = 300;
- public static final int OVERRIDE_ACCESS = 400;
- /** Full access to modify the calendar, but not the access control settings */
- public static final int CONTRIBUTOR_ACCESS = 500;
- public static final int EDITOR_ACCESS = 600;
+ public static final int CAL_ACCESS_READ = 200;
+ /** Can reply yes/no/maybe to an event */
+ public static final int CAL_ACCESS_RESPOND = 300;
+ /** not used */
+ public static final int CAL_ACCESS_OVERRIDE = 400;
+ /** Full access to modify the calendar, but not the access control
+ * settings
+ */
+ public static final int CAL_ACCESS_CONTRIBUTOR = 500;
+ /** Full access to modify the calendar, but not the access control
+ * settings
+ */
+ public static final int CAL_ACCESS_EDITOR = 600;
/** Full access to the calendar */
- public static final int OWNER_ACCESS = 700;
+ public static final int CAL_ACCESS_OWNER = 700;
/** Domain admin */
- public static final int ROOT_ACCESS = 800;
+ public static final int CAL_ACCESS_ROOT = 800;
/**
* Is the calendar selected to be displayed?
+ * 0 - do not show events associated with this calendar.
+ * 1 - show events associated with this calendar
* <P>Type: INTEGER (boolean)</P>
*/
- public static final String SELECTED = "selected";
+ public static final String VISIBLE = "visible";
/**
- * The timezone the calendar's events occurs in
+ * The time zone the calendar is associated with.
* <P>Type: TEXT</P>
*/
- public static final String TIMEZONE = "timezone";
+ public static final String CALENDAR_TIME_ZONE = "calendar_timezone";
/**
- * If this calendar is in the list of calendars that are selected for
- * syncing then "sync_events" is 1, otherwise 0.
+ * Is this calendar synced and are its events stored on the device?
+ * 0 - Do not sync this calendar or store events for this calendar.
+ * 1 - Sync down events for this calendar.
* <p>Type: INTEGER (boolean)</p>
*/
public static final String SYNC_EVENTS = "sync_events";
/**
- * Sync state data.
- * <p>Type: String (blob)</p>
+ * The owner account for this calendar, based on the calendar feed.
+ * This will be different from the _SYNC_ACCOUNT for delegated calendars.
+ * Column name.
+ * <P>Type: String</P>
*/
- public static final String SYNC_STATE = "sync_state";
+ public static final String OWNER_ACCOUNT = "ownerAccount";
/**
- * Whether the row has been deleted. A deleted row should be ignored.
+ * Can the organizer respond to the event? If no, the status of the
+ * organizer should not be shown by the UI. Defaults to 1. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
- public static final String DELETED = "deleted";
+ public static final String CAN_ORGANIZER_RESPOND = "canOrganizerRespond";
+
+ /**
+ * Can the organizer modify the time zone of the event? Column name.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CAN_MODIFY_TIME_ZONE = "canModifyTimeZone";
+
+ /**
+ * The maximum number of reminders allowed for an event. Column name.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MAX_REMINDERS = "maxReminders";
+
+ /**
+ * A comma separated list of reminder methods supported for this
+ * calendar in the format "#,#,#". Valid types are
+ * {@link Reminders#METHOD_DEFAULT}, {@link Reminders#METHOD_ALERT},
+ * {@link Reminders#METHOD_EMAIL}, {@link Reminders#METHOD_SMS}. Column
+ * name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ALLOWED_REMINDERS = "allowedReminders";
}
/**
* Class that represents a Calendar Entity. There is one entry per calendar.
+ * This is a helper class to make batch operations easier.
*/
public static class CalendarsEntity implements BaseColumns, SyncColumns, CalendarsColumns {
+ /**
+ * The default Uri used when creating a new calendar EntityIterator.
+ */
+ @SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
"/calendar_entities");
- public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
- return new EntityIteratorImpl(cursor, resolver);
- }
-
- public static EntityIterator newEntityIterator(Cursor cursor,
- ContentProviderClient provider) {
- return new EntityIteratorImpl(cursor, provider);
+ /**
+ * Creates an entity iterator for the given cursor. It assumes the
+ * cursor contains a calendars query.
+ *
+ * @param cursor query on {@link #CONTENT_URI}
+ * @return an EntityIterator of calendars
+ */
+ public static EntityIterator newEntityIterator(Cursor cursor) {
+ return new EntityIteratorImpl(cursor);
}
private static class EntityIteratorImpl extends CursorEntityIterator {
- private final ContentResolver mResolver;
- private final ContentProviderClient mProvider;
-
- public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
- super(cursor);
- mResolver = resolver;
- mProvider = null;
- }
- public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
+ public EntityIteratorImpl(Cursor cursor) {
super(cursor);
- mResolver = null;
- mProvider = provider;
}
@Override
@@ -282,35 +377,46 @@ public final class Calendar {
ContentValues cv = new ContentValues();
cv.put(_ID, calendarId);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ACCOUNT);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ACCOUNT_TYPE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_NAME);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_TYPE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_TIME);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
- DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
- DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_MARK);
-
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC2);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC3);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC4);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC5);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC3);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC4);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC5);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC6);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC7);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC8);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC9);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC10);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.NAME);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
- Calendars.DISPLAY_NAME);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, Calendars.COLOR);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELECTED);
+ Calendars.CALENDAR_DISPLAY_NAME);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.CALENDAR_COLOR);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ACCESS_LEVEL);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.LOCATION);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TIMEZONE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Calendars.CALENDAR_LOCATION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CALENDAR_TIME_ZONE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Calendars.OWNER_ACCOUNT);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
- Calendars.ORGANIZER_CAN_RESPOND);
+ Calendars.CAN_ORGANIZER_RESPOND);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.CAN_MODIFY_TIME_ZONE);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.MAX_REMINDERS);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.CAN_PARTIALLY_UPDATE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Calendars.ALLOWED_REMINDERS);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
@@ -327,26 +433,40 @@ public final class Calendar {
}
/**
- * Contains a list of available calendars.
+ * Fields and helpers for interacting with Calendars.
*/
- public static class Calendars implements BaseColumns, SyncColumns, AccountColumns,
- CalendarsColumns
- {
- private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars._SYNC_ACCOUNT + "=?"
- + " AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
+ public static class Calendars implements BaseColumns, SyncColumns, CalendarsColumns {
+ private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars.ACCOUNT_NAME + "=?"
+ + " AND "
+ + Calendars.ACCOUNT_TYPE + "=?";
- public static final Cursor query(ContentResolver cr, String[] projection,
- String where, String orderBy)
- {
- return cr.query(CONTENT_URI, projection, where,
- null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ /**
+ * Helper function for generating a calendars query. This is blocking
+ * and should not be used on the UI thread. See
+ * {@link ContentResolver#query(Uri, String[], String, String[], String)}
+ * for more details about using the parameters.
+ *
+ * @param cr The ContentResolver to query with
+ * @param projection A list of columns to return
+ * @param selection A formatted selection string
+ * @param selectionArgs arguments to the selection string
+ * @param orderBy How to order the returned rows
+ * @return
+ */
+ public static final Cursor query(ContentResolver cr, String[] projection, String selection,
+ String[] selectionArgs, String orderBy) {
+ return cr.query(CONTENT_URI, projection, selection, selectionArgs,
+ orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
}
/**
- * Convenience method perform a delete on the Calendar provider
+ * Convenience method perform a delete on the Calendar provider. This is
+ * a blocking call and should not be used on the UI thread.
*
* @param cr the ContentResolver
- * @param selection the rows to delete
+ * @param selection A filter to apply to rows before deleting, formatted
+ * as an SQL WHERE clause (excluding the WHERE itself).
+ * @param selectionArgs Fill in the '?'s in the selection
* @return the count of rows that were deleted
*/
public static int delete(ContentResolver cr, String selection, String[] selectionArgs)
@@ -356,10 +476,12 @@ public final class Calendar {
/**
* Convenience method to delete all calendars that match the account.
+ * This is a blocking call and should not be used on the UI thread.
*
* @param cr the ContentResolver
- * @param account the account whose rows should be deleted
- * @return the count of rows that were deleted
+ * @param account the account whose calendars and events should be
+ * deleted
+ * @return the count of calendar rows that were deleted
*/
public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
// delete all calendars that match this account
@@ -369,8 +491,9 @@ public final class Calendar {
}
/**
- * The content:// style URL for this table
+ * The content:// style URL for accessing Calendars
*/
+ @SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
/**
@@ -379,88 +502,76 @@ public final class Calendar {
public static final String DEFAULT_SORT_ORDER = "displayName";
/**
- * The URL to the calendar
- * <P>Type: TEXT (URL)</P>
- */
- public static final String URL = "url";
-
- /**
- * The URL for the calendar itself
- * <P>Type: TEXT (URL)</P>
- */
- public static final String SELF_URL = "selfUrl";
-
- /**
- * The URL for the calendar to be edited
- * <P>Type: TEXT (URL)</P>
- */
- public static final String EDIT_URL = "editUrl";
-
- /**
- * The URL for the calendar events
- * <P>Type: TEXT (URL)</P>
- */
- public static final String EVENTS_URL = "eventsUrl";
-
- /**
- * The name of the calendar
+ * The name of the calendar. Column name.
* <P>Type: TEXT</P>
*/
public static final String NAME = "name";
/**
- * The display name of the calendar
+ * The default location for the calendar. Column name.
* <P>Type: TEXT</P>
*/
- public static final String DISPLAY_NAME = "displayName";
-
- /**
- * The location the of the events in the calendar
- * <P>Type: TEXT</P>
- */
- public static final String LOCATION = "location";
-
- /**
- * The owner account for this calendar, based on the calendar feed.
- * This will be different from the _SYNC_ACCOUNT for delegated calendars.
- * <P>Type: String</P>
- */
- public static final String OWNER_ACCOUNT = "ownerAccount";
-
- /**
- * Can the organizer respond to the event? If no, the status of the
- * organizer should not be shown by the UI. Defaults to 1
- * <P>Type: INTEGER (boolean)</P>
- */
- public static final String ORGANIZER_CAN_RESPOND = "organizerCanRespond";
+ public static final String CALENDAR_LOCATION = "calendar_location";
+
+ /**
+ * These fields are only writable by a sync adapter. To modify them the
+ * caller must include {@link #CALLER_IS_SYNCADAPTER},
+ * {@link #ACCOUNT_NAME}, and {@link #ACCOUNT_TYPE} in the Uri's query
+ * parameters.
+ */
+ public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
+ ACCOUNT_NAME,
+ ACCOUNT_TYPE,
+ _SYNC_ID,
+ DIRTY,
+ OWNER_ACCOUNT,
+ MAX_REMINDERS,
+ CAN_MODIFY_TIME_ZONE,
+ CAN_ORGANIZER_RESPOND,
+ CAN_PARTIALLY_UPDATE,
+ CALENDAR_LOCATION,
+ CALENDAR_TIME_ZONE,
+ CALENDAR_ACCESS_LEVEL,
+ DELETED,
+ CAL_SYNC1,
+ CAL_SYNC2,
+ CAL_SYNC3,
+ CAL_SYNC4,
+ CAL_SYNC5,
+ CAL_SYNC6,
+ CAL_SYNC7,
+ CAL_SYNC8,
+ CAL_SYNC9,
+ CAL_SYNC10,
+ };
}
/**
* Columns from the Attendees table that other tables join into themselves.
*/
- public interface AttendeesColumns {
+ private interface AttendeesColumns {
/**
- * The id of the event.
+ * The id of the event. Column name.
* <P>Type: INTEGER</P>
*/
public static final String EVENT_ID = "event_id";
/**
- * The name of the attendee.
+ * The name of the attendee. Column name.
* <P>Type: STRING</P>
*/
public static final String ATTENDEE_NAME = "attendeeName";
/**
- * The email address of the attendee.
+ * The email address of the attendee. Column name.
* <P>Type: STRING</P>
*/
public static final String ATTENDEE_EMAIL = "attendeeEmail";
/**
- * The relationship of the attendee to the user.
- * <P>Type: INTEGER (one of {@link #RELATIONSHIP_ATTENDEE}, ...}.
+ * The relationship of the attendee to the user. Column name.
+ * <P>Type: INTEGER (one of {@link #RELATIONSHIP_ATTENDEE}, ...}.</P>
*/
public static final String ATTENDEE_RELATIONSHIP = "attendeeRelationship";
@@ -471,8 +582,8 @@ public final class Calendar {
public static final int RELATIONSHIP_SPEAKER = 4;
/**
- * The type of attendee.
- * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})
+ * The type of attendee. Column name.
+ * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})</P>
*/
public static final String ATTENDEE_TYPE = "attendeeType";
@@ -481,8 +592,8 @@ public final class Calendar {
public static final int TYPE_OPTIONAL = 2;
/**
- * The attendance status of the attendee.
- * <P>Type: Integer (one of {@link #ATTENDEE_STATUS_ACCEPTED}, ...}.
+ * The attendance status of the attendee. Column name.
+ * <P>Type: Integer (one of {@link #ATTENDEE_STATUS_ACCEPTED}, ...).</P>
*/
public static final String ATTENDEE_STATUS = "attendeeStatus";
@@ -493,50 +604,77 @@ public final class Calendar {
public static final int ATTENDEE_STATUS_TENTATIVE = 4;
}
+ /**
+ * Fields and helpers for interacting with Attendees.
+ */
public static final class Attendees implements BaseColumns, AttendeesColumns, EventsColumns {
+
+ /**
+ * The content:// style URL for accessing Attendees data
+ */
+ @SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/attendees");
+ /**
+ * the projection used by the attendees query
+ */
+ public static final String[] PROJECTION = new String[] {
+ _ID, ATTENDEE_NAME, ATTENDEE_EMAIL, ATTENDEE_RELATIONSHIP, ATTENDEE_STATUS,};
+ private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
- // TODO: fill out this class when we actually start utilizing attendees
- // in the calendar application.
+ /**
+ * Queries all attendees associated with the given event. This is a
+ * blocking call and should not be done on the UI thread.
+ *
+ * @param cr The content resolver to use for the query
+ * @param eventId The id of the event to retrieve attendees for
+ * @return A Cursor containing all attendees for the event
+ */
+ public static final Cursor query(ContentResolver cr, long eventId) {
+ String[] attArgs = {Long.toString(eventId)};
+ return cr.query(CONTENT_URI, PROJECTION, ATTENDEES_WHERE, attArgs /* selection args */,
+ null /* sort order */);
+ }
}
/**
* Columns from the Events table that other tables join into themselves.
*/
- public interface EventsColumns {
- /**
- * The calendar the event belongs to
- * <P>Type: INTEGER (foreign key to the Calendars table)</P>
- */
- public static final String CALENDAR_ID = "calendar_id";
+ private interface EventsColumns {
/**
- * The URI for an HTML version of this event.
- * <P>Type: TEXT</P>
+ * The {@link Calendars#_ID} of the calendar the event belongs to.
+ * Column name.
+ * <P>Type: INTEGER</P>
*/
- public static final String HTML_URI = "htmlUri";
+ public static final String CALENDAR_ID = "calendar_id";
/**
- * The title of the event
+ * The title of the event. Column name.
* <P>Type: TEXT</P>
*/
public static final String TITLE = "title";
/**
- * The description of the event
+ * The description of the event. Column name.
* <P>Type: TEXT</P>
*/
public static final String DESCRIPTION = "description";
/**
- * Where the event takes place.
+ * Where the event takes place. Column name.
* <P>Type: TEXT</P>
*/
public static final String EVENT_LOCATION = "eventLocation";
/**
- * The event status
- * <P>Type: INTEGER (int)</P>
+ * A secondary color for the individual event. Column name.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String EVENT_COLOR = "eventColor";
+
+ /**
+ * The event status. Column name.
+ * <P>Type: INTEGER (one of {@link #STATUS_TENTATIVE}...)</P>
*/
public static final String STATUS = "eventStatus";
@@ -548,153 +686,232 @@ public final class Calendar {
* This is a copy of the attendee status for the owner of this event.
* This field is copied here so that we can efficiently filter out
* events that are declined without having to look in the Attendees
- * table.
+ * table. Column name.
*
* <P>Type: INTEGER (int)</P>
*/
public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
/**
- * This column is available for use by sync adapters
+ * This column is available for use by sync adapters. Column name.
* <P>Type: TEXT</P>
*/
- public static final String SYNC_ADAPTER_DATA = "syncAdapterData";
+ public static final String SYNC_DATA1 = "sync_data1";
/**
- * The comments feed uri.
+ * This column is available for use by sync adapters. Column name.
* <P>Type: TEXT</P>
*/
- public static final String COMMENTS_URI = "commentsUri";
+ public static final String SYNC_DATA2 = "sync_data2";
/**
- * The time the event starts
- * <P>Type: INTEGER (long; millis since epoch)</P>
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
*/
- public static final String DTSTART = "dtstart";
+ public static final String SYNC_DATA3 = "sync_data3";
/**
- * The time the event ends
- * <P>Type: INTEGER (long; millis since epoch)</P>
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
*/
- public static final String DTEND = "dtend";
+ public static final String SYNC_DATA4 = "sync_data4";
+
+ /**
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_DATA5 = "sync_data5";
+
+ /**
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_DATA6 = "sync_data6";
+
+ /**
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_DATA7 = "sync_data7";
+
+ /**
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_DATA8 = "sync_data8";
+
+ /**
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_DATA9 = "sync_data9";
+
+ /**
+ * This column is available for use by sync adapters. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_DATA10 = "sync_data10";
+
+ /**
+ * Used to indicate that a row is not a real event but an original copy of a locally
+ * modified event. A copy is made when an event changes from non-dirty to dirty and the
+ * event is on a calendar with {@link Calendars#CAN_PARTIALLY_UPDATE} set to 1. This copy
+ * does not get expanded in the instances table and is only visible in queries made by a
+ * sync adapter. The copy gets removed when the event is changed back to non-dirty by a
+ * sync adapter.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String LAST_SYNCED = "lastSynced";
/**
- * The time the event starts with allDay events in a local tz
+ * The time the event starts in UTC millis since epoch. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
- public static final String DTSTART2 = "dtstart2";
+ public static final String DTSTART = "dtstart";
/**
- * The time the event ends with allDay events in a local tz
+ * The time the event ends in UTC millis since epoch. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
- public static final String DTEND2 = "dtend2";
+ public static final String DTEND = "dtend";
/**
- * The duration of the event
+ * The duration of the event in RFC2445 format. Column name.
* <P>Type: TEXT (duration in RFC2445 format)</P>
*/
public static final String DURATION = "duration";
/**
- * The timezone for the event.
- * <P>Type: TEXT
+ * The timezone for the event. Column name.
+ * <P>Type: TEXT</P>
*/
public static final String EVENT_TIMEZONE = "eventTimezone";
/**
- * The timezone for the event, allDay events will have a local tz instead of UTC
- * <P>Type: TEXT
+ * The timezone for the end time of the event. Column name.
+ * <P>Type: TEXT</P>
*/
- public static final String EVENT_TIMEZONE2 = "eventTimezone2";
+ public static final String EVENT_END_TIMEZONE = "eventEndTimezone";
/**
- * Whether the event lasts all day or not
+ * Is the event all day (time zone independent). Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String ALL_DAY = "allDay";
/**
- * Visibility for the event.
- * <P>Type: INTEGER</P>
+ * Defines how the event shows up for others when the calendar is
+ * shared. Column name.
+ * <P>Type: INTEGER (One of {@link #ACCESS_DEFAULT}, ...)</P>
*/
- public static final String VISIBILITY = "visibility";
-
- public static final int VISIBILITY_DEFAULT = 0;
- public static final int VISIBILITY_CONFIDENTIAL = 1;
- public static final int VISIBILITY_PRIVATE = 2;
- public static final int VISIBILITY_PUBLIC = 3;
+ public static final String ACCESS_LEVEL = "accessLevel";
/**
- * Transparency for the event -- does the event consume time on the calendar?
- * <P>Type: INTEGER</P>
+ * Default access is controlled by the server and will be treated as
+ * public on the device.
*/
- public static final String TRANSPARENCY = "transparency";
+ public static final int ACCESS_DEFAULT = 0;
+ /**
+ * Confidential is not used by the app.
+ */
+ public static final int ACCESS_CONFIDENTIAL = 1;
+ /**
+ * Private shares the event as a free/busy slot with no details.
+ */
+ public static final int ACCESS_PRIVATE = 2;
+ /**
+ * Public makes the contents visible to anyone with access to the
+ * calendar.
+ */
+ public static final int ACCESS_PUBLIC = 3;
- public static final int TRANSPARENCY_OPAQUE = 0;
+ /**
+ * If this event counts as busy time or is still free time that can be
+ * scheduled over. Column name.
+ * <P>Type: INTEGER (One of {@link #AVAILABILITY_BUSY},
+ * {@link #AVAILABILITY_FREE})</P>
+ */
+ public static final String AVAILABILITY = "availability";
- public static final int TRANSPARENCY_TRANSPARENT = 1;
+ /**
+ * Indicates that this event takes up time and will conflict with other
+ * events.
+ */
+ public static final int AVAILABILITY_BUSY = 0;
+ /**
+ * Indicates that this event is free time and will not conflict with
+ * other events.
+ */
+ public static final int AVAILABILITY_FREE = 1;
/**
- * Whether the event has an alarm or not
+ * Whether the event has an alarm or not. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String HAS_ALARM = "hasAlarm";
/**
- * Whether the event has extended properties or not
+ * Whether the event has extended properties or not. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String HAS_EXTENDED_PROPERTIES = "hasExtendedProperties";
/**
- * The recurrence rule for the event.
- * than one.
+ * The recurrence rule for the event. Column name.
* <P>Type: TEXT</P>
*/
public static final String RRULE = "rrule";
/**
- * The recurrence dates for the event.
+ * The recurrence dates for the event. Column name.
* <P>Type: TEXT</P>
*/
public static final String RDATE = "rdate";
/**
- * The recurrence exception rule for the event.
+ * The recurrence exception rule for the event. Column name.
* <P>Type: TEXT</P>
*/
public static final String EXRULE = "exrule";
/**
- * The recurrence exception dates for the event.
+ * The recurrence exception dates for the event. Column name.
* <P>Type: TEXT</P>
*/
public static final String EXDATE = "exdate";
/**
+ * The {@link Events#_ID} of the original recurring event for which this
+ * event is an exception. Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ORIGINAL_ID = "original_id";
+
+ /**
* The _sync_id of the original recurring event for which this event is
- * an exception.
+ * an exception. The provider should keep the original_id in sync when
+ * this is updated. Column name.
* <P>Type: TEXT</P>
*/
- public static final String ORIGINAL_EVENT = "originalEvent";
+ public static final String ORIGINAL_SYNC_ID = "original_sync_id";
/**
* The original instance time of the recurring event for which this
- * event is an exception.
+ * event is an exception. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
/**
* The allDay status (true or false) of the original recurring event
- * for which this event is an exception.
+ * for which this event is an exception. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String ORIGINAL_ALL_DAY = "originalAllDay";
/**
- * The last date this event repeats on, or NULL if it never ends
+ * The last date this event repeats on, or NULL if it never ends. Column
+ * name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String LAST_DATE = "lastDate";
@@ -702,73 +919,80 @@ public final class Calendar {
/**
* Whether the event has attendee information. True if the event
* has full attendee data, false if the event has information about
- * self only.
+ * self only. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String HAS_ATTENDEE_DATA = "hasAttendeeData";
/**
- * Whether guests can modify the event.
+ * Whether guests can modify the event. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String GUESTS_CAN_MODIFY = "guestsCanModify";
/**
- * Whether guests can invite other guests.
+ * Whether guests can invite other guests. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String GUESTS_CAN_INVITE_OTHERS = "guestsCanInviteOthers";
/**
- * Whether guests can see the list of attendees.
+ * Whether guests can see the list of attendees. Column name.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String GUESTS_CAN_SEE_GUESTS = "guestsCanSeeGuests";
/**
- * Email of the organizer (owner) of the event.
+ * Email of the organizer (owner) of the event. Column name.
* <P>Type: STRING</P>
*/
public static final String ORGANIZER = "organizer";
/**
- * Whether the user can invite others to the event.
- * The GUESTS_CAN_INVITE_OTHERS is a setting that applies to an arbitrary guest,
- * while CAN_INVITE_OTHERS indicates if the user can invite others (either through
- * GUESTS_CAN_INVITE_OTHERS or because the user has modify access to the event).
+ * Whether the user can invite others to the event. The
+ * GUESTS_CAN_INVITE_OTHERS is a setting that applies to an arbitrary
+ * guest, while CAN_INVITE_OTHERS indicates if the user can invite
+ * others (either through GUESTS_CAN_INVITE_OTHERS or because the user
+ * has modify access to the event). Column name.
* <P>Type: INTEGER (boolean, readonly)</P>
*/
public static final String CAN_INVITE_OTHERS = "canInviteOthers";
-
- /**
- * The owner account for this calendar, based on the calendar (foreign
- * key into the calendars table).
- * <P>Type: String</P>
- */
- public static final String OWNER_ACCOUNT = "ownerAccount";
-
- /**
- * Whether the row has been deleted. A deleted row should be ignored.
- * <P>Type: INTEGER (boolean)</P>
- */
- public static final String DELETED = "deleted";
}
/**
- * Contains one entry per calendar event. Recurring events show up as a single entry.
+ * Class that represents an Event Entity. There is one entry per event.
+ * Recurring events show up as a single entry. This is a helper class to
+ * make batch operations easier. A {@link ContentResolver} or
+ * {@link ContentProviderClient} is required as the helper does additional
+ * queries to add reminders and attendees to each entry.
*/
- public static final class EventsEntity implements BaseColumns, SyncColumns, AccountColumns,
- EventsColumns {
+ public static final class EventsEntity implements BaseColumns, SyncColumns, EventsColumns {
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
"/event_entities");
+ /**
+ * Creates a new iterator for events
+ *
+ * @param cursor An event query
+ * @param resolver For performing additional queries
+ * @return an EntityIterator containing one entity per event in the
+ * cursor
+ */
public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
return new EntityIteratorImpl(cursor, resolver);
}
+ /**
+ * Creates a new iterator for events
+ *
+ * @param cursor An event query
+ * @param provider For performing additional queries
+ * @return an EntityIterator containing one entity per event in the
+ * cursor
+ */
public static EntityIterator newEntityIterator(Cursor cursor,
ContentProviderClient provider) {
return new EntityIteratorImpl(cursor, provider);
@@ -827,20 +1051,19 @@ public final class Calendar {
ContentValues cv = new ContentValues();
cv.put(Events._ID, eventId);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ID);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HTML_URI);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TITLE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DESCRIPTION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_LOCATION);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, STATUS);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELF_ATTENDEE_STATUS);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, COMMENTS_URI);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTSTART);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTEND);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DURATION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_TIMEZONE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_END_TIMEZONE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBILITY);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, TRANSPARENCY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, AVAILABILITY);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
HAS_EXTENDED_PROPERTIES);
@@ -848,7 +1071,8 @@ public final class Calendar {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RDATE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_EVENT);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_SYNC_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
ORIGINAL_INSTANCE_TIME);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
@@ -860,13 +1084,29 @@ public final class Calendar {
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
- DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, EventsColumns.DELETED);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
- Events.SYNC_ADAPTER_DATA);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA2);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA3);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA4);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA5);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA6);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA7);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA8);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA9);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA10);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC3);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC4);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC5);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC6);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC7);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC8);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC9);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC10);
Entity entity = new Entity(cv);
Cursor subCursor;
@@ -955,64 +1195,139 @@ public final class Calendar {
}
/**
- * Contains one entry per calendar event. Recurring events show up as a single entry.
+ * Fields and helpers for interacting with Events.
*/
- public static final class Events implements BaseColumns, SyncColumns, AccountColumns,
- EventsColumns {
-
- private static final String[] FETCH_ENTRY_COLUMNS =
- new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
-
- private static final String[] ATTENDEES_COLUMNS =
- new String[] { AttendeesColumns.ATTENDEE_NAME,
- AttendeesColumns.ATTENDEE_EMAIL,
- AttendeesColumns.ATTENDEE_RELATIONSHIP,
- AttendeesColumns.ATTENDEE_TYPE,
- AttendeesColumns.ATTENDEE_STATUS };
+ public static final class Events implements BaseColumns, SyncColumns, EventsColumns,
+ CalendarsColumns {
+ /**
+ * Queries all events with the given projection. This is a blocking call
+ * and should not be done on the UI thread.
+ *
+ * @param cr The content resolver to use for the query
+ * @param projection The columns to return
+ * @return A Cursor containing all events in the db
+ */
public static final Cursor query(ContentResolver cr, String[] projection) {
return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
}
- public static final Cursor query(ContentResolver cr, String[] projection,
- String where, String orderBy) {
- return cr.query(CONTENT_URI, projection, where,
- null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- private static String extractValue(ICalendar.Component component,
- String propertyName) {
- ICalendar.Property property =
- component.getFirstProperty(propertyName);
- if (property != null) {
- return property.getValue();
- }
- return null;
+ /**
+ * Queries events using the given projection, selection filter, and
+ * ordering. This is a blocking call and should not be done on the UI
+ * thread. For selection and selectionArgs usage see
+ * {@link ContentResolver#query(Uri, String[], String, String[], String)}
+ *
+ * @param cr The content resolver to use for the query
+ * @param projection The columns to return
+ * @param selection Filter on the query as an SQL WHERE statement
+ * @param selectionArgs Args to replace any '?'s in the selection
+ * @param orderBy How to order the rows as an SQL ORDER BY statement
+ * @return A Cursor containing the matching events
+ */
+ public static final Cursor query(ContentResolver cr, String[] projection, String selection,
+ String[] selectionArgs, String orderBy) {
+ return cr.query(CONTENT_URI, projection, selection, null,
+ orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
}
/**
- * The content:// style URL for this table
+ * The content:// style URL for interacting with events. Appending an
+ * event id using {@link ContentUris#withAppendedId(Uri, long)} will
+ * specify a single event.
*/
+ @SuppressWarnings("hiding")
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/events");
- public static final Uri DELETED_CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/deleted_events");
+ /**
+ * The content:// style URI for recurring event exceptions. Insertions require an
+ * appended event ID. Deletion of exceptions requires both the original event ID and
+ * the exception event ID (see {@link Uri.Builder#appendPath}).
+ */
+ public static final Uri EXCEPTION_CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/exception");
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = "";
+ private static final String DEFAULT_SORT_ORDER = "";
+
+ /**
+ * These are columns that should only ever be updated by the provider,
+ * either because they are views mapped to another table or because they
+ * are used for provider only functionality.
+ */
+ public static String[] PROVIDER_WRITABLE_COLUMNS = new String[] {
+ ACCOUNT_NAME,
+ ACCOUNT_TYPE,
+ CAL_SYNC1,
+ CAL_SYNC2,
+ CAL_SYNC3,
+ CAL_SYNC4,
+ CAL_SYNC5,
+ CAL_SYNC6,
+ CAL_SYNC7,
+ CAL_SYNC8,
+ CAL_SYNC9,
+ CAL_SYNC10,
+ ALLOWED_REMINDERS,
+ CALENDAR_ACCESS_LEVEL,
+ CALENDAR_COLOR,
+ CALENDAR_TIME_ZONE,
+ CAN_MODIFY_TIME_ZONE,
+ CAN_ORGANIZER_RESPOND,
+ CALENDAR_DISPLAY_NAME,
+ CAN_PARTIALLY_UPDATE,
+ SYNC_EVENTS,
+ VISIBLE,
+ };
+
+ /**
+ * These fields are only writable by a sync adapter. To modify them the
+ * caller must include CALLER_IS_SYNCADAPTER, _SYNC_ACCOUNT, and
+ * _SYNC_ACCOUNT_TYPE in the query parameters.
+ */
+ public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
+ _SYNC_ID,
+ DIRTY,
+ SYNC_DATA1,
+ SYNC_DATA2,
+ SYNC_DATA3,
+ SYNC_DATA4,
+ SYNC_DATA5,
+ SYNC_DATA6,
+ SYNC_DATA7,
+ SYNC_DATA8,
+ SYNC_DATA9,
+ SYNC_DATA10,
+ };
}
/**
- * Contains one entry per calendar event instance. Recurring events show up every time
- * they occur.
+ * Fields and helpers for interacting with Instances. An instance is a
+ * single occurrence of an event including time zone specific start and end
+ * days and minutes.
*/
public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
- private static final String WHERE_CALENDARS_SELECTED = Calendars.SELECTED + "=1";
+ private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=1";
+ /**
+ * Performs a query to return all visible instances in the given range.
+ * This is a blocking function and should not be done on the UI thread.
+ * This will cause an expansion of recurring events to fill this time
+ * range if they are not already expanded and will slow down for larger
+ * time ranges with many recurring events.
+ *
+ * @param cr The ContentResolver to use for the query
+ * @param projection The columns to return
+ * @param begin The start of the time range to query in UTC millis since
+ * epoch
+ * @param end The end of the time range to query in UTC millis since
+ * epoch
+ * @return A Cursor containing all instances in the given range
+ */
public static final Cursor query(ContentResolver cr, String[] projection,
long begin, long end) {
Uri.Builder builder = CONTENT_URI.buildUpon();
@@ -1022,111 +1337,184 @@ public final class Calendar {
null, DEFAULT_SORT_ORDER);
}
+ /**
+ * Performs a query to return all visible instances in the given range
+ * that match the given query. This is a blocking function and should
+ * not be done on the UI thread. This will cause an expansion of
+ * recurring events to fill this time range if they are not already
+ * expanded and will slow down for larger time ranges with many
+ * recurring events.
+ *
+ * @param cr The ContentResolver to use for the query
+ * @param projection The columns to return
+ * @param begin The start of the time range to query in UTC millis since
+ * epoch
+ * @param end The end of the time range to query in UTC millis since
+ * epoch
+ * @param searchQuery A string of space separated search terms. Segments
+ * enclosed by double quotes will be treated as a single
+ * term.
+ * @return A Cursor of instances matching the search terms in the given
+ * time range
+ */
public static final Cursor query(ContentResolver cr, String[] projection,
long begin, long end, String searchQuery) {
Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
- return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
- new String[] { searchQuery }, DEFAULT_SORT_ORDER);
+ builder = builder.appendPath(searchQuery);
+ return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED, null,
+ DEFAULT_SORT_ORDER);
}
- public static final Cursor query(ContentResolver cr, String[] projection,
- long begin, long end, String where, String orderBy) {
+ /**
+ * Performs a query to return all visible instances in the given range
+ * that match the given selection. This is a blocking function and
+ * should not be done on the UI thread. This will cause an expansion of
+ * recurring events to fill this time range if they are not already
+ * expanded and will slow down for larger time ranges with many
+ * recurring events.
+ *
+ * @param cr The ContentResolver to use for the query
+ * @param projection The columns to return
+ * @param begin The start of the time range to query in UTC millis since
+ * epoch
+ * @param end The end of the time range to query in UTC millis since
+ * epoch
+ * @param selection Filter on the query as an SQL WHERE statement
+ * @param selectionArgs Args to replace any '?'s in the selection
+ * @param orderBy How to order the rows as an SQL ORDER BY statement
+ * @return A Cursor of instances matching the selection
+ */
+ public static final Cursor query(ContentResolver cr, String[] projection, long begin,
+ long end, String selection, String[] selectionArgs, String orderBy) {
Uri.Builder builder = CONTENT_URI.buildUpon();
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
- if (TextUtils.isEmpty(where)) {
- where = WHERE_CALENDARS_SELECTED;
+ if (TextUtils.isEmpty(selection)) {
+ selection = WHERE_CALENDARS_SELECTED;
} else {
- where = "(" + where + ") AND " + WHERE_CALENDARS_SELECTED;
+ selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
}
- return cr.query(builder.build(), projection, where,
- null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ return cr.query(builder.build(), projection, selection, selectionArgs,
+ orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
}
+ /**
+ * Performs a query to return all visible instances in the given range
+ * that match the given selection. This is a blocking function and
+ * should not be done on the UI thread. This will cause an expansion of
+ * recurring events to fill this time range if they are not already
+ * expanded and will slow down for larger time ranges with many
+ * recurring events.
+ *
+ * @param cr The ContentResolver to use for the query
+ * @param projection The columns to return
+ * @param begin The start of the time range to query in UTC millis since
+ * epoch
+ * @param end The end of the time range to query in UTC millis since
+ * epoch
+ * @param searchQuery A string of space separated search terms. Segments
+ * enclosed by double quotes will be treated as a single
+ * term.
+ * @param selection Filter on the query as an SQL WHERE statement
+ * @param selectionArgs Args to replace any '?'s in the selection
+ * @param orderBy How to order the rows as an SQL ORDER BY statement
+ * @return A Cursor of instances matching the selection
+ */
public static final Cursor query(ContentResolver cr, String[] projection, long begin,
- long end, String searchQuery, String where, String orderBy) {
+ long end, String searchQuery, String selection, String[] selectionArgs,
+ String orderBy) {
Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
builder = builder.appendPath(searchQuery);
- if (TextUtils.isEmpty(where)) {
- where = WHERE_CALENDARS_SELECTED;
+ if (TextUtils.isEmpty(selection)) {
+ selection = WHERE_CALENDARS_SELECTED;
} else {
- where = "(" + where + ") AND " + WHERE_CALENDARS_SELECTED;
+ selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
}
- return cr.query(builder.build(), projection, where, null,
+ return cr.query(builder.build(), projection, selection, selectionArgs,
orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
}
/**
- * The content:// style URL for this table
+ * The content:// style URL for querying an instance range. The begin
+ * and end of the range to query should be added as path segments if
+ * this is used directly.
*/
+ @SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
"/instances/when");
+ /**
+ * The content:// style URL for querying an instance range by Julian
+ * Day. The start and end day should be added as path segments if this
+ * is used directly.
+ */
public static final Uri CONTENT_BY_DAY_URI =
Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
+ /**
+ * The content:// style URL for querying an instance range with a search
+ * term. The begin, end, and search string should be appended as path
+ * segments if this is used directly.
+ */
public static final Uri CONTENT_SEARCH_URI = Uri.parse("content://" + AUTHORITY +
"/instances/search");
+ /**
+ * The content:// style URL for querying an instance range with a search
+ * term. The start day, end day, and search string should be appended as
+ * path segments if this is used directly.
+ */
public static final Uri CONTENT_SEARCH_BY_DAY_URI =
Uri.parse("content://" + AUTHORITY + "/instances/searchbyday");
/**
* The default sort order for this table.
*/
- public static final String DEFAULT_SORT_ORDER = "begin ASC";
+ private static final String DEFAULT_SORT_ORDER = "begin ASC";
/**
- * The sort order is: events with an earlier start time occur
- * first and if the start times are the same, then events with
- * a later end time occur first. The later end time is ordered
- * first so that long-running events in the calendar views appear
- * first. If the start and end times of two events are
- * the same then we sort alphabetically on the title. This isn't
- * required for correctness, it just adds a nice touch.
- */
- public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
- /**
- * The beginning time of the instance, in UTC milliseconds
+ * The beginning time of the instance, in UTC milliseconds. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String BEGIN = "begin";
/**
- * The ending time of the instance, in UTC milliseconds
+ * The ending time of the instance, in UTC milliseconds. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String END = "end";
/**
- * The event for this instance
+ * The _id of the event for this instance. Column name.
* <P>Type: INTEGER (long, foreign key to the Events table)</P>
*/
public static final String EVENT_ID = "event_id";
/**
- * The Julian start day of the instance, relative to the local timezone
+ * The Julian start day of the instance, relative to the local time
+ * zone. Column name.
* <P>Type: INTEGER (int)</P>
*/
public static final String START_DAY = "startDay";
/**
- * The Julian end day of the instance, relative to the local timezone
+ * The Julian end day of the instance, relative to the local time
+ * zone. Column name.
* <P>Type: INTEGER (int)</P>
*/
public static final String END_DAY = "endDay";
/**
* The start minute of the instance measured from midnight in the
- * local timezone.
+ * local time zone. Column name.
* <P>Type: INTEGER (int)</P>
*/
public static final String START_MINUTE = "startMinute";
/**
* The end minute of the instance measured from midnight in the
- * local timezone.
+ * local time zone. Column name.
* <P>Type: INTEGER (int)</P>
*/
public static final String END_MINUTE = "endMinute";
@@ -1134,14 +1522,12 @@ public final class Calendar {
/**
* CalendarCache stores some settings for calendar including the current
- * time zone for the app. These settings are stored using a key/value
+ * time zone for the instaces. These settings are stored using a key/value
* scheme.
*/
- public interface CalendarCacheColumns {
+ private interface CalendarCacheColumns {
/**
- * The key for the setting. Keys are defined in CalendarChache in the
- * Calendar provider.
- * TODO Add keys to this file
+ * The key for the setting. Keys are defined in {@link CalendarCache}.
*/
public static final String KEY = "key";
@@ -1211,7 +1597,7 @@ public final class Calendar {
* the Instances table and these are all stored in the first (and only)
* row of the CalendarMetaData table.
*/
- public interface CalendarMetaDataColumns {
+ private interface CalendarMetaDataColumns {
/**
* The local timezone that was used for precomputing the fields
* in the Instances table.
@@ -1245,34 +1631,51 @@ public final class Calendar {
public static final String MAX_EVENTDAYS = "maxEventDays";
}
+ /**
+ * @hide
+ */
public static final class CalendarMetaData implements CalendarMetaDataColumns, BaseColumns {
}
- public interface EventDaysColumns {
+ private interface EventDaysColumns {
/**
- * The Julian starting day number.
+ * The Julian starting day number. Column name.
* <P>Type: INTEGER (int)</P>
*/
public static final String STARTDAY = "startDay";
+ /**
+ * The Julian ending day number. Column name.
+ * <P>Type: INTEGER (int)</P>
+ */
public static final String ENDDAY = "endDay";
}
+ /**
+ * Fields and helpers for querying for a list of days that contain events.
+ */
public static final class EventDays implements EventDaysColumns {
- public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
- "/instances/groupbyday");
+ private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ + "/instances/groupbyday");
+ /**
+ * The projection used by the EventDays query.
+ */
public static final String[] PROJECTION = { STARTDAY, ENDDAY };
- public static final String SELECTION = "selected=1";
+ private static final String SELECTION = "selected=1";
/**
- * Retrieves the days with events for the Julian days starting at "startDay"
- * for "numDays".
+ * Retrieves the days with events for the Julian days starting at
+ * "startDay" for "numDays". It returns a cursor containing startday and
+ * endday representing the max range of days for all events beginning on
+ * each startday.This is a blocking function and should not be done on
+ * the UI thread.
*
* @param cr the ContentResolver
* @param startDay the first Julian day in the range
* @param numDays the number of days to load (must be at least 1)
- * @return a database cursor
+ * @return a database cursor containing a list of start and end days for
+ * events
*/
public static final Cursor query(ContentResolver cr, int startDay, int numDays) {
if (numDays < 1) {
@@ -1287,9 +1690,9 @@ public final class Calendar {
}
}
- public interface RemindersColumns {
+ private interface RemindersColumns {
/**
- * The event the reminder belongs to
+ * The event the reminder belongs to. Column name.
* <P>Type: INTEGER (foreign key to the Events table)</P>
*/
public static final String EVENT_ID = "event_id";
@@ -1297,17 +1700,24 @@ public final class Calendar {
/**
* The minutes prior to the event that the alarm should ring. -1
* specifies that we should use the default value for the system.
+ * Column name.
* <P>Type: INTEGER</P>
*/
public static final String MINUTES = "minutes";
+ /**
+ * Passing this as a minutes value will use the default reminder
+ * minutes.
+ */
public static final int MINUTES_DEFAULT = -1;
/**
- * The alarm method, as set on the server. DEFAULT, ALERT, EMAIL, and
- * SMS are possible values; the device will only process DEFAULT and
- * ALERT reminders (the other types are simply stored so we can send the
- * same reminder info back to the server when we make changes).
+ * The alarm method, as set on the server. {@link #METHOD_DEFAULT},
+ * {@link #METHOD_ALERT}, {@link #METHOD_EMAIL}, and {@link #METHOD_SMS}
+ * are possible values; the device will only process
+ * {@link #METHOD_DEFAULT} and {@link #METHOD_ALERT} reminders (the
+ * other types are simply stored so we can send the same reminder info
+ * back to the server when we make changes).
*/
public static final String METHOD = "method";
@@ -1317,61 +1727,85 @@ public final class Calendar {
public static final int METHOD_SMS = 3;
}
+ /**
+ * Fields and helpers for accessing reminders for an event.
+ */
public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
- public static final String TABLE_NAME = "Reminders";
+ private static final String REMINDERS_WHERE = Calendar.Reminders.EVENT_ID + "=?";
+ /**
+ * The projection used by the reminders query.
+ */
+ public static final String[] PROJECTION = new String[] {
+ _ID, MINUTES, METHOD,};
+ @SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
+
+ /**
+ * Queries all reminders associated with the given event. This is a
+ * blocking call and should not be done on the UI thread.
+ *
+ * @param cr The content resolver to use for the query
+ * @param eventId The id of the event to retrieve reminders for
+ * @return A Cursor containing all reminders for the event
+ */
+ public static final Cursor query(ContentResolver cr, long eventId) {
+ String[] remArgs = {Long.toString(eventId)};
+ return cr.query(CONTENT_URI, PROJECTION, REMINDERS_WHERE, remArgs /* selection args */,
+ null /* sort order */);
+ }
}
- public interface CalendarAlertsColumns {
+ private interface CalendarAlertsColumns {
/**
- * The event that the alert belongs to
+ * The event that the alert belongs to. Column name.
* <P>Type: INTEGER (foreign key to the Events table)</P>
*/
public static final String EVENT_ID = "event_id";
/**
- * The start time of the event, in UTC
+ * The start time of the event, in UTC. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String BEGIN = "begin";
/**
- * The end time of the event, in UTC
+ * The end time of the event, in UTC. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String END = "end";
/**
- * The alarm time of the event, in UTC
+ * The alarm time of the event, in UTC. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String ALARM_TIME = "alarmTime";
/**
* The creation time of this database entry, in UTC.
- * (Useful for debugging missed reminders.)
+ * Useful for debugging missed reminders. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String CREATION_TIME = "creationTime";
/**
* The time that the alarm broadcast was received by the Calendar app,
- * in UTC. (Useful for debugging missed reminders.)
+ * in UTC. Useful for debugging missed reminders. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String RECEIVED_TIME = "receivedTime";
/**
* The time that the notification was created by the Calendar app,
- * in UTC. (Useful for debugging missed reminders.)
+ * in UTC. Useful for debugging missed reminders. Column name.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String NOTIFY_TIME = "notifyTime";
/**
- * The state of this alert. It starts out as SCHEDULED, then when
- * the alarm goes off, it changes to FIRED, and then when the user
- * dismisses the alarm it changes to DISMISSED.
+ * The state of this alert. It starts out as {@link #SCHEDULED}, then
+ * when the alarm goes off, it changes to {@link #FIRED}, and then when
+ * the user dismisses the alarm it changes to {@link #DISMISSED}. Column
+ * name.
* <P>Type: INTEGER</P>
*/
public static final String STATE = "state";
@@ -1381,21 +1815,33 @@ public final class Calendar {
public static final int DISMISSED = 2;
/**
- * The number of minutes that this alarm precedes the start time
- * <P>Type: INTEGER </P>
+ * The number of minutes that this alarm precedes the start time. Column
+ * name.
+ * <P>Type: INTEGER</P>
*/
public static final String MINUTES = "minutes";
/**
- * The default sort order for this table
+ * The default sort order for this alerts queries
*/
public static final String DEFAULT_SORT_ORDER = "begin ASC,title ASC";
}
+ /**
+ * Fields and helpers for accessing calendar alerts information. These
+ * fields are for tracking which alerts have been fired.
+ */
public static final class CalendarAlerts implements BaseColumns,
CalendarAlertsColumns, EventsColumns, CalendarsColumns {
+ /**
+ * @hide
+ */
public static final String TABLE_NAME = "CalendarAlerts";
+ /**
+ * The Uri for querying calendar alert information
+ */
+ @SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
"/calendar_alerts");
@@ -1422,6 +1868,11 @@ public final class Calendar {
private static final boolean DEBUG = true;
+ /**
+ * Helper for inserting an alarm time associated with an event
+ *
+ * @hide
+ */
public static final Uri insert(ContentResolver cr, long eventId,
long begin, long end, long alarmTime, int minutes) {
ContentValues values = new ContentValues();
@@ -1438,6 +1889,19 @@ public final class Calendar {
return cr.insert(CONTENT_URI, values);
}
+ /**
+ * Queries alerts info using the given projection, selection filter, and
+ * ordering. This is a blocking call and should not be done on the UI
+ * thread. For selection and selectionArgs usage see
+ * {@link ContentResolver#query(Uri, String[], String, String[], String)}
+ *
+ * @param cr The content resolver to use for the query
+ * @param projection The columns to return
+ * @param selection Filter on the query as an SQL WHERE statement
+ * @param selectionArgs Args to replace any '?'s in the selection
+ * @param sortOrder How to order the rows as an SQL ORDER BY statement
+ * @return A Cursor containing the matching alerts
+ */
public static final Cursor query(ContentResolver cr, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
return cr.query(CONTENT_URI, projection, selection, selectionArgs,
@@ -1446,12 +1910,13 @@ public final class Calendar {
/**
* Finds the next alarm after (or equal to) the given time and returns
- * the time of that alarm or -1 if no such alarm exists.
+ * the time of that alarm or -1 if no such alarm exists. This is a
+ * blocking call and should not be done on the UI thread.
*
* @param cr the ContentResolver
* @param millis the time in UTC milliseconds
* @return the next alarm time greater than or equal to "millis", or -1
- * if no such alarm exists.
+ * if no such alarm exists.
*/
public static final long findNextAlarmTime(ContentResolver cr, long millis) {
String selection = ALARM_TIME + ">=" + millis;
@@ -1534,6 +1999,17 @@ public final class Calendar {
}
}
+ /**
+ * Schedules an alarm intent with the system AlarmManager that will
+ * cause the Calendar provider to recheck alarms. This is used to wake
+ * the Calendar alarm handler when an alarm is expected or to do a
+ * periodic refresh of alarm data.
+ *
+ * @param context A context for referencing system resources
+ * @param manager The AlarmManager to use or null
+ * @param alarmTime The time to fire the intent in UTC millis since
+ * epoch
+ */
public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
if (DEBUG) {
Time time = new Time();
@@ -1593,27 +2069,32 @@ public final class Calendar {
}
}
- public interface ExtendedPropertiesColumns {
+ private interface ExtendedPropertiesColumns {
/**
- * The event the extended property belongs to
+ * The event the extended property belongs to. Column name.
* <P>Type: INTEGER (foreign key to the Events table)</P>
*/
public static final String EVENT_ID = "event_id";
/**
* The name of the extended property. This is a uri of the form
- * {scheme}#{local-name} convention.
+ * {scheme}#{local-name} convention. Column name.
* <P>Type: TEXT</P>
*/
public static final String NAME = "name";
/**
- * The value of the extended property.
+ * The value of the extended property. Column name.
* <P>Type: TEXT</P>
*/
public static final String VALUE = "value";
}
+ /**
+ * Fields for accessing the Extended Properties. This is a generic set of
+ * name/value pairs for use by sync adapters or apps to add extra
+ * information to events.
+ */
public static final class ExtendedProperties implements BaseColumns,
ExtendedPropertiesColumns, EventsColumns {
public static final Uri CONTENT_URI =
@@ -1634,7 +2115,7 @@ public final class Calendar {
*/
private SyncState() {}
- public static final String CONTENT_DIRECTORY =
+ private static final String CONTENT_DIRECTORY =
SyncStateContract.Constants.CONTENT_DIRECTORY;
/**
@@ -1647,39 +2128,43 @@ public final class Calendar {
/**
* Columns from the EventsRawTimes table
*/
- public interface EventsRawTimesColumns {
+ private interface EventsRawTimesColumns {
/**
- * The corresponding event id
+ * The corresponding event id. Column name.
* <P>Type: INTEGER (long)</P>
*/
public static final String EVENT_ID = "event_id";
/**
- * The RFC2445 compliant time the event starts
+ * The RFC2445 compliant time the event starts. Column name.
* <P>Type: TEXT</P>
*/
public static final String DTSTART_2445 = "dtstart2445";
/**
- * The RFC2445 compliant time the event ends
+ * The RFC2445 compliant time the event ends. Column name.
* <P>Type: TEXT</P>
*/
public static final String DTEND_2445 = "dtend2445";
/**
- * The RFC2445 compliant original instance time of the recurring event for which this
- * event is an exception.
+ * The RFC2445 compliant original instance time of the recurring event
+ * for which this event is an exception. Column name.
* <P>Type: TEXT</P>
*/
public static final String ORIGINAL_INSTANCE_TIME_2445 = "originalInstanceTime2445";
/**
- * The RFC2445 compliant last date this event repeats on, or NULL if it never ends
+ * The RFC2445 compliant last date this event repeats on, or NULL if it
+ * never ends. Column name.
* <P>Type: TEXT</P>
*/
public static final String LAST_DATE_2445 = "lastDate2445";
}
+ /**
+ * @hide
+ */
public static final class EventsRawTimes implements BaseColumns, EventsRawTimesColumns {
}
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index bf051f5..02faf49 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -77,9 +77,17 @@ public class CallLog {
*/
public static final String TYPE = "type";
+ /** Call log type for incoming calls. */
public static final int INCOMING_TYPE = 1;
+ /** Call log type for outgoing calls. */
public static final int OUTGOING_TYPE = 2;
+ /** Call log type for missed calls. */
public static final int MISSED_TYPE = 3;
+ /**
+ * Call log type for voicemails.
+ * @hide
+ */
+ public static final int VOICEMAIL_TYPE = 4;
/**
* The phone number as the user entered it.
@@ -143,6 +151,13 @@ public class CallLog {
public static final String CACHED_NUMBER_LABEL = "numberlabel";
/**
+ * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}.
+ * <P>Type: TEXT</P>
+ * @hide
+ */
+ public static final String VOICEMAIL_URI = "voicemail_uri";
+
+ /**
* Adds a call to the call log.
*
* @param ci the CallerInfo object to get the target contact from. Can be null
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4f88612..6c14119 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -122,6 +122,17 @@ public final class ContactsContract {
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
/**
+ * An optional URI parameter for selection queries that instructs the
+ * provider to include the user's personal profile contact entry (if any)
+ * in the contact results. If present, the user's profile will always be
+ * the first entry returned. The default value is false.
+ *
+ * Specifying this parameter will result in a security error if the calling
+ * application does not have android.permission.READ_PROFILE permission.
+ */
+ public static final String INCLUDE_PROFILE = "include_profile";
+
+ /**
* A query parameter key used to specify the package that is requesting a query.
* This is used for restricting data based on package name.
*
@@ -144,6 +155,27 @@ public final class ContactsContract {
public static final String LIMIT_PARAM_KEY = "limit";
/**
+ * A query parameter specifing a primary account. This parameter should be used with
+ * {@link #PRIMARY_ACCOUNT_TYPE}. The contacts provider handling a query may rely on
+ * this information to optimize its query results.
+ *
+ * For example, in an email composition screen, its implementation can specify an account when
+ * obtaining possible recipients, letting the provider know which account is selected during
+ * the composition. The provider may use the "primary account" information to optimize
+ * the search result.
+ * @hide
+ */
+ public static final String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
+
+ /**
+ * A query parameter specifing a primary account. This parameter should be used with
+ * {@link #PRIMARY_ACCOUNT_NAME}. See the doc in {@link #PRIMARY_ACCOUNT_NAME}.
+ * @hide
+ */
+ public static final String PRIMARY_ACCOUNT_TYPE = "type_for_primary_account";
+
+
+ /**
* @hide
*/
public static final class Preferences {
@@ -742,12 +774,18 @@ public final class ContactsContract {
public static final String PHOTO_THUMBNAIL_URI = "photo_thumb_uri";
/**
- * Lookup value that reflects the {@link Groups#GROUP_VISIBLE} state of
- * any {@link CommonDataKinds.GroupMembership} for this contact.
+ * Flag that reflects the {@link Groups#GROUP_VISIBLE} state of any
+ * {@link CommonDataKinds.GroupMembership} for this contact.
*/
public static final String IN_VISIBLE_GROUP = "in_visible_group";
/**
+ * Flag that reflects whether this contact represents the user's
+ * personal profile entry.
+ */
+ public static final String IS_USER_PROFILE = "is_user_profile";
+
+ /**
* An indicator of whether this contact has at least one phone number. "1" if there is
* at least one phone number, "0" otherwise.
* <P>Type: INTEGER</P>
@@ -1264,7 +1302,7 @@ public final class ContactsContract {
* Base {@link Uri} for referencing multiple {@link Contacts} entry,
* created by appending {@link #LOOKUP_KEY} using
* {@link Uri#withAppendedPath(Uri, String)}. The lookup keys have to be
- * encoded and joined with the colon (":") seperator. The resulting string
+ * encoded and joined with the colon (":") separator. The resulting string
* has to be encoded again. Provides
* {@link OpenableColumns} columns when queried, or returns the
* referenced contact formatted as a vCard when opened through
@@ -1717,6 +1755,88 @@ public final class ContactsContract {
}
}
+ /**
+ * <p>
+ * Constants for the user's profile data, which is represented as a single contact on
+ * the device that represents the user. The profile contact is not aggregated
+ * together automatically in the same way that normal contacts are; instead, each
+ * account on the device may contribute a single raw contact representing the user's
+ * personal profile data from that source.
+ * </p>
+ * <p>
+ * Access to the profile entry through these URIs (or incidental access to parts of
+ * the profile if retrieved directly via ID) requires additional permissions beyond
+ * the read/write contact permissions required by the provider. Querying for profile
+ * data requires android.permission.READ_PROFILE permission, and inserting or
+ * updating profile data requires android.permission.WRITE_PROFILE permission.
+ * </p>
+ * <h3>Operations</h3>
+ * <dl>
+ * <dt><b>Insert</b></dt>
+ * <dd>The user's profile entry cannot be created explicitly (attempting to do so
+ * will throw an exception). When a raw contact is inserted into the profile, the
+ * provider will check for the existence of a profile on the device. If one is
+ * found, the raw contact's {@link RawContacts#CONTACT_ID} column gets the _ID of
+ * the profile Contact. If no match is found, the profile Contact is created and
+ * its _ID is put into the {@link RawContacts#CONTACT_ID} column of the newly
+ * inserted raw contact.</dd>
+ * <dt><b>Update</b></dt>
+ * <dd>The profile Contact has the same update restrictions as Contacts in general,
+ * but requires the android.permission.WRITE_PROFILE permission.</dd>
+ * <dt><b>Delete</b></dt>
+ * <dd>The profile Contact cannot be explicitly deleted. It will be removed
+ * automatically if all of its constituent raw contact entries are deleted.</dd>
+ * <dt><b>Query</b></dt>
+ * <dd>
+ * <ul>
+ * <li>The {@link #CONTENT_URI} for profiles behaves in much the same way as
+ * retrieving a contact by ID, except that it will only ever return the user's
+ * profile contact.
+ * </li>
+ * <li>
+ * The profile contact supports all of the same sub-paths as an individual contact
+ * does - the content of the profile contact can be retrieved as entities or
+ * data rows. Similarly, specific raw contact entries can be retrieved by appending
+ * the desired raw contact ID within the profile.
+ * </li>
+ * </ul>
+ * </dd>
+ * </dl>
+ */
+ public static final class Profile implements BaseColumns, ContactsColumns,
+ ContactOptionsColumns, ContactNameColumns, ContactStatusColumns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private Profile() {
+ }
+
+ /**
+ * The content:// style URI for this table, which requests the contact entry
+ * representing the user's personal profile data.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "profile");
+
+ /**
+ * {@link Uri} for referencing the user's profile {@link Contacts} entry,
+ * Provides {@link OpenableColumns} columns when queried, or returns the
+ * user's profile contact formatted as a vCard when opened through
+ * {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
+ */
+ public static final Uri CONTENT_VCARD_URI = Uri.withAppendedPath(CONTENT_URI,
+ "as_vcard");
+
+ /**
+ * {@link Uri} for referencing the raw contacts that make up the user's profile
+ * {@link Contacts} entry. An individual raw contact entry within the profile
+ * can be addressed by appending the raw contact ID. The entities or data within
+ * that specific raw contact can be requested by appending the entity or data
+ * path as well.
+ */
+ public static final Uri CONTENT_RAW_CONTACTS_URI = Uri.withAppendedPath(CONTENT_URI,
+ "raw_contacts");
+ }
+
protected interface RawContactsColumns {
/**
* A reference to the {@link ContactsContract.Contacts#_ID} that this
@@ -1785,6 +1905,12 @@ public final class ContactsContract {
* <P>Type: INTEGER</P>
*/
public static final String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
+
+ /**
+ * Flag that reflects whether this raw contact belongs to the user's
+ * personal profile entry.
+ */
+ public static final String RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile";
}
/**
@@ -6135,6 +6261,95 @@ public final class ContactsContract {
}
/**
+ * <p>
+ * API allowing applications to send usage information for each {@link Data} row to the
+ * Contacts Provider.
+ * </p>
+ * <p>
+ * With the feedback, Contacts Provider may return more contextually appropriate results for
+ * Data listing, typically supplied with
+ * {@link ContactsContract.Contacts#CONTENT_FILTER_URI},
+ * {@link ContactsContract.CommonDataKinds.Email#CONTENT_FILTER_URI},
+ * {@link ContactsContract.CommonDataKinds.Phone#CONTENT_FILTER_URI}, and users can benefit
+ * from better ranked (sorted) lists in applications that show auto-complete list.
+ * </p>
+ * <p>
+ * There is no guarantee for how this feedback is used, or even whether it is used at all.
+ * The ranking algorithm will make best efforts to use the feedback data, but the exact
+ * implementation, the storage data structures as well as the resulting sort order is device
+ * and version specific and can change over time.
+ * </p>
+ * <p>
+ * When updating usage information, users of this API need to use
+ * {@link ContentResolver#update(Uri, ContentValues, String, String[])} with a Uri constructed
+ * from {@link DataUsageFeedback#FEEDBACK_URI}. The Uri must contain one or more data id(s) as
+ * its last path. They also need to append a query parameter to the Uri, to specify the type of
+ * the communication, which enables the Contacts Provider to differentiate between kinds of
+ * interactions using the same contact data field (for example a phone number can be used to
+ * make phone calls or send SMS).
+ * </p>
+ * <p>
+ * Selection and selectionArgs are ignored and must be set to null. To get data ids,
+ * you may need to call {@link ContentResolver#query(Uri, String[], String, String[], String)}
+ * toward {@link Data#CONTENT_URI}.
+ * </p>
+ * <p>
+ * {@link ContentResolver#update(Uri, ContentValues, String, String[])} returns a positive
+ * integer when successful, and returns 0 if no contact with that id was found.
+ * </p>
+ * <p>
+ * Example:
+ * <pre>
+ * Uri uri = DataUsageFeedback.UPDATE_URI.buildUpon()
+ * .appendPath(TextUtils.join(",", dataIds))
+ * .appendQueryParameter(DataUsageFeedback.METHOD, DataUsageFeedback.METHOD_CALL)
+ * .build();
+ * boolean successful = resolver.update(uri, new ContentValues(), null, null) > 0;
+ * </pre>
+ * </p>
+ * @hide
+ */
+ public static final class DataUsageFeedback {
+
+ /**
+ * The content:// style URI for sending usage feedback.
+ * Must be used with {@link ContentResolver#update(Uri, ContentValues, String, String[])}.
+ */
+ public static final Uri FEEDBACK_URI =
+ Uri.withAppendedPath(Data.CONTENT_URI, "usagefeedback");
+
+ /**
+ * <p>
+ * Name for query parameter specifying the type of data usage.
+ * </p>
+ */
+ public static final String USAGE_TYPE = "type";
+
+ /**
+ * <p>
+ * Type of usage for voice interaction, which includes phone call, voice chat, and
+ * video chat.
+ * </p>
+ */
+ public static final String USAGE_TYPE_CALL = "call";
+
+ /**
+ * <p>
+ * Type of usage for text interaction involving longer messages, which includes email.
+ * </p>
+ */
+ public static final String USAGE_TYPE_LONG_TEXT = "long_text";
+
+ /**
+ * <p>
+ * Type of usage for text interaction involving shorter messages, which includes SMS,
+ * text chat with email addresses.
+ * </p>
+ */
+ public static final String USAGE_TYPE_SHORT_TEXT = "short_text";
+ }
+
+ /**
* Helper methods to display QuickContact dialogs that allow users to pivot on
* a specific {@link Contacts} entry.
*/
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index c9b2f97..3bc1348 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -166,7 +166,6 @@ public final class MediaStore {
* If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
* value of EXTRA_OUTPUT.
* @see #EXTRA_OUTPUT
- * @see #EXTRA_VIDEO_QUALITY
*/
public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
@@ -181,6 +180,9 @@ public final class MediaStore {
* written to the standard location for videos, and the Uri of that location will be
* returned in the data field of the Uri.
* @see #EXTRA_OUTPUT
+ * @see #EXTRA_VIDEO_QUALITY
+ * @see #EXTRA_SIZE_LIMIT
+ * @see #EXTRA_DURATION_LIMIT
*/
public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
@@ -1094,12 +1096,6 @@ public final class MediaStore {
public static final String ALBUM_KEY = "album_key";
/**
- * A URI to the album art, if any
- * <P>Type: TEXT</P>
- */
- public static final String ALBUM_ART = "album_art";
-
- /**
* The track number of this song on the album, if any.
* This number encodes both the track number and the
* disc number. For multi-disc sets, this number will
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index eb9eb03..6ab7738 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -41,7 +41,6 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.AndroidException;
-import android.util.Config;
import android.util.Log;
import java.net.URISyntaxException;
@@ -569,7 +568,7 @@ public final class Settings {
public static final String AUTHORITY = "settings";
private static final String TAG = "Settings";
- private static final boolean LOCAL_LOGV = Config.LOGV || false;
+ private static final boolean LOCAL_LOGV = false || false;
public static class SettingNotFoundException extends AndroidException {
public SettingNotFoundException(String msg) {
@@ -1103,6 +1102,18 @@ public final class Settings {
public static final int END_BUTTON_BEHAVIOR_DEFAULT = END_BUTTON_BEHAVIOR_SLEEP;
/**
+ * Is advanced settings mode turned on. 0 == no, 1 == yes
+ * @hide
+ */
+ public static final String ADVANCED_SETTINGS = "advanced_settings";
+
+ /**
+ * ADVANCED_SETTINGS default value.
+ * @hide
+ */
+ public static final int ADVANCED_SETTINGS_DEFAULT = 0;
+
+ /**
* Whether Airplane Mode is on.
*/
public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
@@ -1494,6 +1505,13 @@ public final class Settings {
public static final Uri DEFAULT_ALARM_ALERT_URI = getUriFor(ALARM_ALERT);
/**
+ * Persistent store for the system default media button event receiver.
+ *
+ * @hide
+ */
+ public static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
+
+ /**
* Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_REPLACE = "auto_replace";
@@ -2906,6 +2924,30 @@ public final class Settings {
public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms";
/**
+ * Setting to turn off walled garden test on Wi-Fi. Feature is enabled by default and
+ * the setting needs to be set to 0 to disable it.
+ * @hide
+ */
+ public static final String WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED =
+ "wifi_watchdog_walled_garden_test_enabled";
+
+ /**
+ * The URL used for walled garden check upon a new conection. WifiWatchdogService
+ * fetches the URL and checks to see if {@link #WIFI_WATCHDOG_WALLED_GARDEN_PATTERN}
+ * is not part of the title string to notify the user on the presence of a walled garden.
+ * @hide
+ */
+ public static final String WIFI_WATCHDOG_WALLED_GARDEN_URL =
+ "wifi_watchdog_walled_garden_url";
+
+ /**
+ * The pattern string in the fetched URL used to detect a walled garden
+ * @hide
+ */
+ public static final String WIFI_WATCHDOG_WALLED_GARDEN_PATTERN =
+ "wifi_watchdog_walled_garden_pattern";
+
+ /**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
* A value of N means that we will make N+1 connection attempts in all.
@@ -3321,12 +3363,20 @@ public final class Settings {
public static final String WIFI_IDLE_MS = "wifi_idle_ms";
/**
- * The interval in milliseconds to issue scans when the driver is
- * started. This is necessary to allow wifi to connect to an
- * access point when the driver is suspended.
+ * The interval in milliseconds to issue wake up scans when wifi needs
+ * to connect. This is necessary to connect to an access point when
+ * device is on the move and the screen is off.
* @hide
*/
- public static final String WIFI_SCAN_INTERVAL_MS = "wifi_scan_interval_ms";
+ public static final String WIFI_FRAMEWORK_SCAN_INTERVAL_MS =
+ "wifi_framework_scan_interval_ms";
+
+ /**
+ * The interval in milliseconds to scan as used by the wifi supplicant
+ * @hide
+ */
+ public static final String WIFI_SUPPLICANT_SCAN_INTERVAL_MS =
+ "wifi_supplicant_scan_interval_ms";
/**
* The interval in milliseconds at which to check packet counts on the
@@ -3729,6 +3779,36 @@ public final class Settings {
"setup_prepaid_detection_redir_host";
/**
+ * The user's preferred "dream" (interactive screensaver) component.
+ *
+ * This component will be launched by the PhoneWindowManager after the user's chosen idle
+ * timeout (specified by {@link #DREAM_TIMEOUT}).
+ * @hide
+ */
+ public static final String DREAM_COMPONENT =
+ "dream_component";
+
+ /**
+ * The delay before a "dream" is started (set to 0 to disable).
+ * @hide
+ */
+ public static final String DREAM_TIMEOUT =
+ "dream_timeout";
+
+ /** {@hide} */
+ public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval";
+ /** {@hide} */
+ public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold";
+ /** {@hide} */
+ public static final String NETSTATS_SUMMARY_BUCKET_DURATION = "netstats_summary_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_SUMMARY_MAX_HISTORY = "netstats_summary_max_history";
+ /** {@hide} */
+ public static final String NETSTATS_DETAIL_BUCKET_DURATION = "netstats_detail_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_DETAIL_MAX_HISTORY = "netstats_detail_max_history";
+
+ /**
* @hide
*/
public static final String[] SETTINGS_TO_BACKUP = {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 3a06b61..6585e82 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -28,7 +28,6 @@ import android.net.Uri;
import android.os.Environment;
import android.telephony.SmsMessage;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import android.util.Patterns;
@@ -46,7 +45,7 @@ import java.util.regex.Pattern;
public final class Telephony {
private static final String TAG = "Telephony";
private static final boolean DEBUG = true;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
// Constructor
public Telephony() {
@@ -90,12 +89,18 @@ public final class Telephony {
public static final String PERSON_ID = "person";
/**
- * The date the message was sent
+ * The date the message was received
* <P>Type: INTEGER (long)</P>
*/
public static final String DATE = "date";
/**
+ * The date the message was sent
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
* Has the message been read
* <P>Type: INTEGER (boolean)</P>
*/
@@ -690,12 +695,18 @@ public final class Telephony {
public static final int MESSAGE_BOX_OUTBOX = 4;
/**
- * The date the message was sent.
+ * The date the message was received.
* <P>Type: INTEGER (long)</P>
*/
public static final String DATE = "date";
/**
+ * The date the message was sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
* The box which the message belong to, for example, MESSAGE_BOX_INBOX.
* <P>Type: INTEGER</P>
*/
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
new file mode 100644
index 0000000..c397af9
--- /dev/null
+++ b/core/java/android/provider/VoicemailContract.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.provider;
+
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.CallLog.Calls;
+/**
+ * The contract between the voicemail provider and applications. Contains
+ * definitions for the supported URIs and columns.
+ *
+ * <P>Voicemails are inserted by what is called as a "voicemail source"
+ * application, which is responsible for syncing voicemail data between a remote
+ * server and the local voicemail content provider. "voicemail source"
+ * application should use the source specific {@link #CONTENT_URI_SOURCE} URI
+ * to insert and retrieve voicemails.
+ *
+ * <P>In addition to the {@link ContentObserver} notifications the voicemail
+ * provider also generates broadcast intents to notify change for applications
+ * that are not active and therefore cannot listen to ContentObserver
+ * notifications. Broadcast intents with following actions are generated:
+ * <ul>
+ * <li> {@link #ACTION_NEW_VOICEMAIL} is generated for each new voicemail
+ * inserted.
+ * </li>
+ * <li> {@link Intent#ACTION_PROVIDER_CHANGED} is generated for any change
+ * made into the database, including new voicemail.
+ * </li>
+ * </ul>
+ * @hide
+ */
+// TODO: unhide when the API is approved by android-api-council
+public class VoicemailContract {
+ /** The authority used by the voicemail provider. */
+ public static final String AUTHORITY = "com.android.voicemail";
+
+ /** URI to insert/retrieve all voicemails. */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/voicemail");
+ /** URI to insert/retrieve voicemails by a given voicemai source. */
+ public static final Uri CONTENT_URI_SOURCE =
+ Uri.parse("content://" + AUTHORITY + "/voicemail/source/");
+
+ // TODO: Move ACTION_NEW_VOICEMAIL to the Intent class.
+ /** Broadcast intent when a new voicemail record is inserted. */
+ public static final String ACTION_NEW_VOICEMAIL = "android.intent.action.NEW_VOICEMAIL";
+ /**
+ * Extra included in {@value Intent#ACTION_PROVIDER_CHANGED} and
+ * {@value #ACTION_NEW_VOICEMAIL} broadcast intents to indicate the package
+ * that caused the change in content provider.
+ * <p>Receivers of the broadcast can use this field to determine if this is
+ * a self change.
+ */
+ public static final String EXTRA_CHANGED_BY = "com.android.voicemail.extra.CHANGED_BY";
+
+ /** The mime type for a collection of voicemails. */
+ public static final String DIR_TYPE =
+ "vnd.android.cursor.dir/voicemails";
+
+ public static final class Voicemails implements BaseColumns {
+ /**
+ * Phone number of the voicemail sender.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NUMBER = Calls.NUMBER;
+ /**
+ * The date the voicemail was sent, in milliseconds since the epoch
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = Calls.DATE;
+ /**
+ * The duration of the voicemail in seconds.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DURATION = Calls.DURATION;
+ /**
+ * Whether this is a new voicemail (i.e. has not been heard).
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String NEW = Calls.NEW;
+ /**
+ * The mail box state of the voicemail.
+ * <P> Possible values: {@link #STATE_INBOX}, {@link #STATE_DELETED},
+ * {@link #STATE_UNDELETED}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String STATE = "state";
+ /** Value of {@link #STATE} when the voicemail is in inbox. */
+ public static int STATE_INBOX = 0;
+ /** Value of {@link #STATE} when the voicemail has been marked as deleted. */
+ public static int STATE_DELETED = 1;
+ /** Value of {@link #STATE} when the voicemail has marked as undeleted. */
+ public static int STATE_UNDELETED = 2;
+ /**
+ * Package name of the source application that inserted the voicemail.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SOURCE_PACKAGE = "source_package";
+ /**
+ * Application-specific data available to the source application that
+ * inserted the voicemail. This is typically used to store the source
+ * specific message id to identify this voicemail on the remote
+ * voicemail server.
+ * <P>Type: TEXT</P>
+ * <P> Note that this is NOT the voicemail media content data.
+ */
+ public static final String SOURCE_DATA = "provider_data";
+ /**
+ * Whether the media content for this voicemail is available for
+ * consumption.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String HAS_CONTENT = "has_content";
+ /**
+ * MIME type of the media content for the voicemail.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MIME_TYPE = "mime_type";
+ /**
+ * Path to the media content file. Internal only field.
+ * @hide
+ */
+ public static final String _DATA = "_data";
+ }
+}
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 132c346..ca2212c 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -83,19 +83,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
onBluetoothDisable();
break;
}
- } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
- int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
- BluetoothDevice.ERROR);
- switch(bondState) {
- case BluetoothDevice.BOND_BONDED:
- if (getPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) {
- setPriority(device, BluetoothA2dp.PRIORITY_ON);
- }
- break;
- case BluetoothDevice.BOND_NONE:
- setPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
- break;
- }
} else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
synchronized (this) {
if (mAudioDevices.containsKey(device)) {
@@ -158,7 +145,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
mAdapter = BluetoothAdapter.getDefaultAdapter();
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
diff --git a/core/java/android/server/BluetoothAdapterProperties.java b/core/java/android/server/BluetoothAdapterProperties.java
index ae8104b..9723f60 100644
--- a/core/java/android/server/BluetoothAdapterProperties.java
+++ b/core/java/android/server/BluetoothAdapterProperties.java
@@ -76,14 +76,13 @@ class BluetoothAdapterProperties {
for (int i = 0; i < properties.length; i++) {
String name = properties[i];
String newValue = null;
- int len;
if (name == null) {
Log.e(TAG, "Error:Adapter Property at index " + i + " is null");
continue;
}
if (name.equals("Devices") || name.equals("UUIDs")) {
StringBuilder str = new StringBuilder();
- len = Integer.valueOf(properties[++i]);
+ int len = Integer.valueOf(properties[++i]);
for (int j = 0; j < len; j++) {
str.append(properties[++i]);
str.append(",");
diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java
index 2304a70..a36cd24 100644
--- a/core/java/android/server/BluetoothBondState.java
+++ b/core/java/android/server/BluetoothBondState.java
@@ -18,6 +18,9 @@ package android.server;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothHeadset;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
@@ -68,6 +71,8 @@ class BluetoothBondState {
private final Context mContext;
private final BluetoothService mService;
private final BluetoothInputProfileHandler mBluetoothInputProfileHandler;
+ private BluetoothA2dp mA2dpProxy;
+ private BluetoothHeadset mHeadsetProxy;
BluetoothBondState(Context context, BluetoothService service) {
mContext = context;
@@ -126,14 +131,15 @@ class BluetoothBondState {
if (state == BluetoothDevice.BOND_BONDED) {
mService.addProfileState(address);
+ } else if (state == BluetoothDevice.BOND_BONDING) {
+ if (mA2dpProxy == null || mHeadsetProxy == null) {
+ getProfileProxy();
+ }
} else if (state == BluetoothDevice.BOND_NONE) {
mService.removeProfileState(address);
}
- // HID is handled by BluetoothService, other profiles
- // will be handled by their respective services.
- mBluetoothInputProfileHandler.setInitialInputDevicePriority(
- mService.getRemoteDevice(address), state);
+ setProfilePriorities(address, state);
if (DBG) {
Log.d(TAG, address + " bond state " + oldState + " -> " + state
@@ -261,6 +267,52 @@ class BluetoothBondState {
mPinAttempt.put(address, new Integer(newAttempt));
}
+ private void getProfileProxy() {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (mA2dpProxy == null) {
+ bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
+ BluetoothProfile.A2DP);
+ }
+
+ if (mHeadsetProxy == null) {
+ bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
+ BluetoothProfile.HEADSET);
+ }
+ }
+
+ private void closeProfileProxy() {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (mA2dpProxy != null) {
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpProxy);
+ }
+
+ if (mHeadsetProxy != null) {
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetProxy);
+ }
+ }
+
+ private BluetoothProfile.ServiceListener mProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (profile == BluetoothProfile.A2DP) {
+ mA2dpProxy = (BluetoothA2dp) proxy;
+ } else if (profile == BluetoothProfile.HEADSET) {
+ mHeadsetProxy = (BluetoothHeadset) proxy;
+ }
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (profile == BluetoothProfile.A2DP) {
+ mA2dpProxy = null;
+ } else if (profile == BluetoothProfile.HEADSET) {
+ mHeadsetProxy = null;
+ }
+ }
+ };
+
private void copyAutoPairingData() {
FileInputStream in = null;
FileOutputStream out = null;
@@ -365,4 +417,30 @@ class BluetoothBondState {
}
}
}
+
+ // Set service priority of Hid, A2DP and Headset profiles depending on
+ // the bond state change
+ private void setProfilePriorities(String address, int state) {
+ BluetoothDevice remoteDevice = mService.getRemoteDevice(address);
+ // HID is handled by BluetoothService
+ mBluetoothInputProfileHandler.setInitialInputDevicePriority(remoteDevice, state);
+
+ // Set service priority of A2DP and Headset
+ // We used to do the priority change in the 2 services after the broadcast
+ // intent reach them. But that left a small time gap that could reject
+ // incoming connection due to undefined priorities.
+ if (state == BluetoothDevice.BOND_BONDED) {
+ if (mA2dpProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
+ mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
+ }
+
+ if (mHeadsetProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
+ mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
+ }
+ } else if (state == BluetoothDevice.BOND_NONE) {
+ mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
+ mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
+ }
+ }
+
}
diff --git a/core/java/android/server/BluetoothInputProfileHandler.java b/core/java/android/server/BluetoothInputProfileHandler.java
index e6513fd..247e297 100644
--- a/core/java/android/server/BluetoothInputProfileHandler.java
+++ b/core/java/android/server/BluetoothInputProfileHandler.java
@@ -60,7 +60,7 @@ final class BluetoothInputProfileHandler {
return sInstance;
}
- synchronized boolean connectInputDevice(BluetoothDevice device,
+ boolean connectInputDevice(BluetoothDevice device,
BluetoothDeviceProfileState state) {
String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
if (objectPath == null ||
@@ -78,7 +78,7 @@ final class BluetoothInputProfileHandler {
return false;
}
- synchronized boolean connectInputDeviceInternal(BluetoothDevice device) {
+ boolean connectInputDeviceInternal(BluetoothDevice device) {
String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
if (!mBluetoothService.connectInputDeviceNative(objectPath)) {
@@ -88,7 +88,7 @@ final class BluetoothInputProfileHandler {
return true;
}
- synchronized boolean disconnectInputDevice(BluetoothDevice device,
+ boolean disconnectInputDevice(BluetoothDevice device,
BluetoothDeviceProfileState state) {
String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
if (objectPath == null ||
@@ -105,7 +105,7 @@ final class BluetoothInputProfileHandler {
return false;
}
- synchronized boolean disconnectInputDeviceInternal(BluetoothDevice device) {
+ boolean disconnectInputDeviceInternal(BluetoothDevice device) {
String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
if (!mBluetoothService.disconnectInputDeviceNative(objectPath)) {
@@ -115,31 +115,31 @@ final class BluetoothInputProfileHandler {
return true;
}
- synchronized int getInputDeviceConnectionState(BluetoothDevice device) {
+ int getInputDeviceConnectionState(BluetoothDevice device) {
if (mInputDevices.get(device) == null) {
return BluetoothInputDevice.STATE_DISCONNECTED;
}
return mInputDevices.get(device);
}
- synchronized List<BluetoothDevice> getConnectedInputDevices() {
+ List<BluetoothDevice> getConnectedInputDevices() {
List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(
new int[] {BluetoothInputDevice.STATE_CONNECTED});
return devices;
}
- synchronized List<BluetoothDevice> getInputDevicesMatchingConnectionStates(int[] states) {
+ List<BluetoothDevice> getInputDevicesMatchingConnectionStates(int[] states) {
List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(states);
return devices;
}
- synchronized int getInputDevicePriority(BluetoothDevice device) {
+ int getInputDevicePriority(BluetoothDevice device) {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()),
BluetoothInputDevice.PRIORITY_UNDEFINED);
}
- synchronized boolean setInputDevicePriority(BluetoothDevice device, int priority) {
+ boolean setInputDevicePriority(BluetoothDevice device, int priority) {
if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
return false;
}
@@ -148,7 +148,7 @@ final class BluetoothInputProfileHandler {
priority);
}
- synchronized List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
+ List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device: mInputDevices.keySet()) {
@@ -163,7 +163,7 @@ final class BluetoothInputProfileHandler {
return inputDevices;
}
- private synchronized void handleInputDeviceStateChange(BluetoothDevice device, int state) {
+ private void handleInputDeviceStateChange(BluetoothDevice device, int state) {
int prevState;
if (mInputDevices.get(device) == null) {
prevState = BluetoothInputDevice.STATE_DISCONNECTED;
@@ -194,7 +194,7 @@ final class BluetoothInputProfileHandler {
mBluetoothService.sendConnectionStateChange(device, state, prevState);
}
- synchronized void handleInputDevicePropertyChange(String address, boolean connected) {
+ void handleInputDevicePropertyChange(String address, boolean connected) {
int state = connected ? BluetoothInputDevice.STATE_CONNECTED :
BluetoothInputDevice.STATE_DISCONNECTED;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -202,7 +202,7 @@ final class BluetoothInputProfileHandler {
handleInputDeviceStateChange(device, state);
}
- synchronized void setInitialInputDevicePriority(BluetoothDevice device, int state) {
+ void setInitialInputDevicePriority(BluetoothDevice device, int state) {
switch (state) {
case BluetoothDevice.BOND_BONDED:
if (getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_UNDEFINED) {
diff --git a/core/java/android/server/BluetoothPanProfileHandler.java b/core/java/android/server/BluetoothPanProfileHandler.java
index 8925856..0d63e19 100644
--- a/core/java/android/server/BluetoothPanProfileHandler.java
+++ b/core/java/android/server/BluetoothPanProfileHandler.java
@@ -76,17 +76,17 @@ final class BluetoothPanProfileHandler {
}
}
- static synchronized BluetoothPanProfileHandler getInstance(Context context,
+ static BluetoothPanProfileHandler getInstance(Context context,
BluetoothService service) {
if (sInstance == null) sInstance = new BluetoothPanProfileHandler(context, service);
return sInstance;
}
- synchronized boolean isTetheringOn() {
+ boolean isTetheringOn() {
return mTetheringOn;
}
- synchronized boolean allowIncomingTethering() {
+ boolean allowIncomingTethering() {
if (isTetheringOn() && getConnectedPanDevices().size() < mMaxPanDevices)
return true;
return false;
@@ -94,7 +94,7 @@ final class BluetoothPanProfileHandler {
private BroadcastReceiver mTetheringReceiver = null;
- synchronized void setBluetoothTethering(boolean value) {
+ void setBluetoothTethering(boolean value) {
if (!value) {
disconnectPanServerDevices();
}
@@ -104,7 +104,7 @@ final class BluetoothPanProfileHandler {
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mTetheringReceiver = new BroadcastReceiver() {
@Override
- public synchronized void onReceive(Context context, Intent intent) {
+ public void onReceive(Context context, Intent intent) {
if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
== BluetoothAdapter.STATE_ON) {
mTetheringOn = true;
@@ -118,7 +118,7 @@ final class BluetoothPanProfileHandler {
}
}
- synchronized int getPanDeviceConnectionState(BluetoothDevice device) {
+ int getPanDeviceConnectionState(BluetoothDevice device) {
BluetoothPanDevice panDevice = mPanDevices.get(device);
if (panDevice == null) {
return BluetoothPan.STATE_DISCONNECTED;
@@ -126,7 +126,7 @@ final class BluetoothPanProfileHandler {
return panDevice.mState;
}
- synchronized boolean connectPanDevice(BluetoothDevice device) {
+ boolean connectPanDevice(BluetoothDevice device) {
String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
if (DBG) Log.d(TAG, "connect PAN(" + objectPath + ")");
if (getPanDeviceConnectionState(device) != BluetoothPan.STATE_DISCONNECTED) {
@@ -158,7 +158,7 @@ final class BluetoothPanProfileHandler {
}
}
- private synchronized boolean disconnectPanServerDevices() {
+ private boolean disconnectPanServerDevices() {
debugLog("disconnect all PAN devices");
for (BluetoothDevice device: mPanDevices.keySet()) {
@@ -187,7 +187,7 @@ final class BluetoothPanProfileHandler {
return true;
}
- synchronized List<BluetoothDevice> getConnectedPanDevices() {
+ List<BluetoothDevice> getConnectedPanDevices() {
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device: mPanDevices.keySet()) {
@@ -198,7 +198,7 @@ final class BluetoothPanProfileHandler {
return devices;
}
- synchronized List<BluetoothDevice> getPanDevicesMatchingConnectionStates(int[] states) {
+ List<BluetoothDevice> getPanDevicesMatchingConnectionStates(int[] states) {
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device: mPanDevices.keySet()) {
@@ -213,7 +213,7 @@ final class BluetoothPanProfileHandler {
return devices;
}
- synchronized boolean disconnectPanDevice(BluetoothDevice device) {
+ boolean disconnectPanDevice(BluetoothDevice device) {
String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
debugLog("disconnect PAN(" + objectPath + ")");
@@ -249,7 +249,7 @@ final class BluetoothPanProfileHandler {
return true;
}
- synchronized void handlePanDeviceStateChange(BluetoothDevice device,
+ void handlePanDeviceStateChange(BluetoothDevice device,
String iface, int state, int role) {
int prevState;
String ifaceAddr = null;
@@ -304,7 +304,7 @@ final class BluetoothPanProfileHandler {
mBluetoothService.sendConnectionStateChange(device, state, prevState);
}
- synchronized void handlePanDeviceStateChange(BluetoothDevice device,
+ void handlePanDeviceStateChange(BluetoothDevice device,
int state, int role) {
handlePanDeviceStateChange(device, null, state, role);
}
@@ -343,7 +343,7 @@ final class BluetoothPanProfileHandler {
}
// configured when we start tethering
- private synchronized String enableTethering(String iface) {
+ private String enableTethering(String iface) {
debugLog("updateTetherState:" + iface);
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index b3cbf50..60bee9a 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -1928,120 +1928,163 @@ public class BluetoothService extends IBluetooth.Stub {
}
/**** Handlers for PAN Profile ****/
+ // TODO: This needs to be converted to a state machine.
- public synchronized boolean isTetheringOn() {
+ public boolean isTetheringOn() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothPanProfileHandler.isTetheringOn();
+ synchronized (mBluetoothPanProfileHandler) {
+ return mBluetoothPanProfileHandler.isTetheringOn();
+ }
}
- /*package*/ synchronized boolean allowIncomingTethering() {
- return mBluetoothPanProfileHandler.allowIncomingTethering();
+ /*package*/boolean allowIncomingTethering() {
+ synchronized (mBluetoothPanProfileHandler) {
+ return mBluetoothPanProfileHandler.allowIncomingTethering();
+ }
}
- public synchronized void setBluetoothTethering(boolean value) {
+ public void setBluetoothTethering(boolean value) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- mBluetoothPanProfileHandler.setBluetoothTethering(value);
+ synchronized (mBluetoothPanProfileHandler) {
+ mBluetoothPanProfileHandler.setBluetoothTethering(value);
+ }
}
- public synchronized int getPanDeviceConnectionState(BluetoothDevice device) {
+ public int getPanDeviceConnectionState(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothPanProfileHandler.getPanDeviceConnectionState(device);
+ synchronized (mBluetoothPanProfileHandler) {
+ return mBluetoothPanProfileHandler.getPanDeviceConnectionState(device);
+ }
}
- public synchronized boolean connectPanDevice(BluetoothDevice device) {
+ public boolean connectPanDevice(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- return mBluetoothPanProfileHandler.connectPanDevice(device);
+ synchronized (mBluetoothPanProfileHandler) {
+ return mBluetoothPanProfileHandler.connectPanDevice(device);
+ }
}
- public synchronized List<BluetoothDevice> getConnectedPanDevices() {
+ public List<BluetoothDevice> getConnectedPanDevices() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothPanProfileHandler.getConnectedPanDevices();
+ synchronized (mBluetoothPanProfileHandler) {
+ return mBluetoothPanProfileHandler.getConnectedPanDevices();
+ }
}
- public synchronized List<BluetoothDevice> getPanDevicesMatchingConnectionStates(
+ public List<BluetoothDevice> getPanDevicesMatchingConnectionStates(
int[] states) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothPanProfileHandler.getPanDevicesMatchingConnectionStates(states);
+ synchronized (mBluetoothPanProfileHandler) {
+ return mBluetoothPanProfileHandler.getPanDevicesMatchingConnectionStates(states);
+ }
}
- public synchronized boolean disconnectPanDevice(BluetoothDevice device) {
+ public boolean disconnectPanDevice(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- return mBluetoothPanProfileHandler.disconnectPanDevice(device);
+ synchronized (mBluetoothPanProfileHandler) {
+ return mBluetoothPanProfileHandler.disconnectPanDevice(device);
+ }
}
- /*package*/ synchronized void handlePanDeviceStateChange(BluetoothDevice device,
+ /*package*/void handlePanDeviceStateChange(BluetoothDevice device,
String iface,
int state,
int role) {
- mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, iface, state, role);
+ synchronized (mBluetoothPanProfileHandler) {
+ mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, iface, state, role);
+ }
}
- /*package*/ synchronized void handlePanDeviceStateChange(BluetoothDevice device,
+ /*package*/void handlePanDeviceStateChange(BluetoothDevice device,
int state, int role) {
- mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, null, state, role);
+ synchronized (mBluetoothPanProfileHandler) {
+ mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, null, state, role);
+ }
}
/**** Handlers for Input Device Profile ****/
+ // This needs to be converted to state machine
- public synchronized boolean connectInputDevice(BluetoothDevice device) {
+ public boolean connectInputDevice(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
- return mBluetoothInputProfileHandler.connectInputDevice(device, state);
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.connectInputDevice(device, state);
+ }
}
- public synchronized boolean connectInputDeviceInternal(BluetoothDevice device) {
- return mBluetoothInputProfileHandler.connectInputDeviceInternal(device);
+ public boolean connectInputDeviceInternal(BluetoothDevice device) {
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.connectInputDeviceInternal(device);
+ }
}
- public synchronized boolean disconnectInputDevice(BluetoothDevice device) {
+ public boolean disconnectInputDevice(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
- return mBluetoothInputProfileHandler.disconnectInputDevice(device, state);
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.disconnectInputDevice(device, state);
+ }
}
- public synchronized boolean disconnectInputDeviceInternal(BluetoothDevice device) {
- return mBluetoothInputProfileHandler.disconnectInputDeviceInternal(device);
+ public boolean disconnectInputDeviceInternal(BluetoothDevice device) {
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.disconnectInputDeviceInternal(device);
+ }
}
- public synchronized int getInputDeviceConnectionState(BluetoothDevice device) {
+ public int getInputDeviceConnectionState(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothInputProfileHandler.getInputDeviceConnectionState(device);
-
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.getInputDeviceConnectionState(device);
+ }
}
- public synchronized List<BluetoothDevice> getConnectedInputDevices() {
+ public List<BluetoothDevice> getConnectedInputDevices() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothInputProfileHandler.getConnectedInputDevices();
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.getConnectedInputDevices();
+ }
}
- public synchronized List<BluetoothDevice> getInputDevicesMatchingConnectionStates(
+ public List<BluetoothDevice> getInputDevicesMatchingConnectionStates(
int[] states) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothInputProfileHandler.getInputDevicesMatchingConnectionStates(states);
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.getInputDevicesMatchingConnectionStates(states);
+ }
}
- public synchronized int getInputDevicePriority(BluetoothDevice device) {
+ public int getInputDevicePriority(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBluetoothInputProfileHandler.getInputDevicePriority(device);
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.getInputDevicePriority(device);
+ }
}
- public synchronized boolean setInputDevicePriority(BluetoothDevice device, int priority) {
+ public boolean setInputDevicePriority(BluetoothDevice device, int priority) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- return mBluetoothInputProfileHandler.setInputDevicePriority(device, priority);
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.setInputDevicePriority(device, priority);
+ }
}
- /*package*/synchronized List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
- return mBluetoothInputProfileHandler.lookupInputDevicesMatchingStates(states);
+ /*package*/List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
+ synchronized (mBluetoothInputProfileHandler) {
+ return mBluetoothInputProfileHandler.lookupInputDevicesMatchingStates(states);
+ }
}
- /*package*/ synchronized void handleInputDevicePropertyChange(String address, boolean connected) {
- mBluetoothInputProfileHandler.handleInputDevicePropertyChange(address, connected);
+ /*package*/void handleInputDevicePropertyChange(String address, boolean connected) {
+ synchronized (mBluetoothInputProfileHandler) {
+ mBluetoothInputProfileHandler.handleInputDevicePropertyChange(address, connected);
+ }
}
public boolean connectHeadset(String address) {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 20661d7..eae7574 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -51,7 +51,7 @@ import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.view.WindowManagerPolicy;
@@ -650,7 +650,7 @@ public abstract class WallpaperService extends Service {
mWindowToken = wrapper.mWindowToken;
mSurfaceHolder.setSizeFromLayout();
mInitializing = true;
- mSession = ViewRoot.getWindowSession(getMainLooper());
+ mSession = ViewAncestor.getWindowSession(getMainLooper());
mWindow.setSession(mSession);
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
index 5eb71d7..bdb3ba9 100644
--- a/core/java/android/speech/RecognitionListener.java
+++ b/core/java/android/speech/RecognitionListener.java
@@ -70,7 +70,8 @@ public interface RecognitionListener {
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList&lt;String&gt;} format use {@link Bundle#getStringArrayList(String)} with
- * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
+ * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter. A float array of
+ * confidence values might also be given in {@link SpeechRecognizer#CONFIDENCE_SCORES}.
*/
void onResults(Bundle results);
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 02c324c..fd709f2 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -46,7 +46,7 @@ public class RecognizerIntent {
}
/**
- * Starts an activity that will prompt the user for speech and sends it through a
+ * Starts an activity that will prompt the user for speech and send it through a
* speech recognizer. The results will be returned via activity results (in
* {@link Activity#onActivityResult}, if you start the intent using
* {@link Activity#startActivityForResult(Intent, int)}), or forwarded via a PendingIntent
@@ -81,8 +81,8 @@ public class RecognizerIntent {
public static final String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH";
/**
- * Starts an activity that will prompt the user for speech, sends it through a
- * speech recognizer, and invokes and either displays a web search result or triggers
+ * Starts an activity that will prompt the user for speech, send it through a
+ * speech recognizer, and either display a web search result or trigger
* another type of action based on the user's speech.
*
* <p>If you want to avoid triggering any type of action besides web search, you can use
@@ -100,11 +100,13 @@ public class RecognizerIntent {
* <li>{@link #EXTRA_MAX_RESULTS}
* <li>{@link #EXTRA_PARTIAL_RESULTS}
* <li>{@link #EXTRA_WEB_SEARCH_ONLY}
+ * <li>{@link #EXTRA_ORIGIN}
* </ul>
*
* <p> Result extras (returned in the result, not to be specified in the request):
* <ul>
* <li>{@link #EXTRA_RESULTS}
+ * <li>{@link #EXTRA_CONFIDENCE_SCORES} (optional)
* </ul>
*
* <p>NOTE: There may not be any applications installed to handle this action, so you should
@@ -181,6 +183,13 @@ public class RecognizerIntent {
* {@link java.util.Locale#getDefault()}.
*/
public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
+
+ /**
+ * Optional value which can be used to indicate the referer url of a page in which
+ * speech was requested. For example, a web browser may choose to provide this for
+ * uses of speech on a given page.
+ */
+ public static final String EXTRA_ORIGIN = "android.speech.extra.ORIGIN";
/**
* Optional limit on the maximum number of results to return. If omitted the recognizer
@@ -232,13 +241,31 @@ public class RecognizerIntent {
/**
* An ArrayList&lt;String&gt; of the recognition results when performing
- * {@link #ACTION_RECOGNIZE_SPEECH}. Returned in the results; not to be specified in the
- * recognition request. Only present when {@link Activity#RESULT_OK} is returned in
- * an activity result. In a PendingIntent, the lack of this extra indicates failure.
+ * {@link #ACTION_RECOGNIZE_SPEECH}. Generally this list should be ordered in
+ * descending order of speech recognizer confidence. (See {@link #EXTRA_CONFIDENCE_SCORES}).
+ * Returned in the results; not to be specified in the recognition request. Only present
+ * when {@link Activity#RESULT_OK} is returned in an activity result. In a PendingIntent,
+ * the lack of this extra indicates failure.
*/
public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
/**
+ * A float array of confidence scores of the recognition results when performing
+ * {@link #ACTION_RECOGNIZE_SPEECH}. The array should be the same size as the ArrayList
+ * returned in {@link #EXTRA_RESULTS}, and should contain values ranging from 0.0 to 1.0,
+ * or -1 to represent an unavailable confidence score.
+ * <p>
+ * Confidence values close to 1.0 indicate high confidence (the speech recognizer is
+ * confident that the recognition result is correct), while values close to 0.0 indicate
+ * low confidence.
+ * <p>
+ * Returned in the results; not to be specified in the recognition request. This extra is
+ * optional and might not be provided. Only present when {@link Activity#RESULT_OK} is
+ * returned in an activity result.
+ */
+ public static final String EXTRA_CONFIDENCE_SCORES = "android.speech.extra.CONFIDENCE_SCORES";
+
+ /**
* Returns the broadcast intent to fire with
* {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, Bundle)}
* to receive details from the package that implements voice search.
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index cd73ba8..8fee41d 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -50,12 +50,26 @@ public class SpeechRecognizer {
private static final String TAG = "SpeechRecognizer";
/**
- * Used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
+ * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
* {@link RecognitionListener#onResults(Bundle)} and
* {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
* recognition results, where the first element is the most likely candidate.
*/
public static final String RESULTS_RECOGNITION = "results_recognition";
+
+ /**
+ * Key used to retrieve a float array from the {@link Bundle} passed to the
+ * {@link RecognitionListener#onResults(Bundle)} and
+ * {@link RecognitionListener#onPartialResults(Bundle)} methods. The array should be
+ * the same size as the ArrayList provided in {@link #RESULTS_RECOGNITION}, and should contain
+ * values ranging from 0.0 to 1.0, or -1 to represent an unavailable confidence score.
+ * <p>
+ * Confidence values close to 1.0 indicate high confidence (the speech recognizer is confident
+ * that the recognition result is correct), while values close to 0.0 indicate low confidence.
+ * <p>
+ * This value is optional and might not be provided.
+ */
+ public static final String CONFIDENCE_SCORES = "confidence_scores";
/** Network operation timed out. */
public static final int ERROR_NETWORK_TIMEOUT = 1;
diff --git a/core/java/android/speech/srec/Recognizer.java b/core/java/android/speech/srec/Recognizer.java
index a03a36a..8a2bc7d 100644
--- a/core/java/android/speech/srec/Recognizer.java
+++ b/core/java/android/speech/srec/Recognizer.java
@@ -22,7 +22,6 @@
package android.speech.srec;
-import android.util.Config;
import android.util.Log;
import java.io.File;
diff --git a/core/java/android/speech/tts/AbstractSynthesisCallback.java b/core/java/android/speech/tts/AbstractSynthesisCallback.java
new file mode 100644
index 0000000..c7a4af0
--- /dev/null
+++ b/core/java/android/speech/tts/AbstractSynthesisCallback.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+/**
+ * Defines additional methods the synthesis callback must implement that
+ * are private to the TTS service implementation.
+ */
+abstract class AbstractSynthesisCallback implements SynthesisCallback {
+ /**
+ * Checks whether the synthesis request completed successfully.
+ */
+ abstract boolean isDone();
+
+ /**
+ * Aborts the speech request.
+ *
+ * Can be called from multiple threads.
+ */
+ abstract void stop();
+}
diff --git a/core/java/android/speech/tts/AudioMessageParams.java b/core/java/android/speech/tts/AudioMessageParams.java
new file mode 100644
index 0000000..68d8738
--- /dev/null
+++ b/core/java/android/speech/tts/AudioMessageParams.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+
+class AudioMessageParams extends MessageParams {
+ private final BlockingMediaPlayer mPlayer;
+
+ AudioMessageParams(UtteranceCompletedDispatcher dispatcher,
+ String callingApp, BlockingMediaPlayer player) {
+ super(dispatcher, callingApp);
+ mPlayer = player;
+ }
+
+ BlockingMediaPlayer getPlayer() {
+ return mPlayer;
+ }
+
+ @Override
+ int getType() {
+ return TYPE_AUDIO;
+ }
+
+}
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
new file mode 100644
index 0000000..a3686b7
--- /dev/null
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.util.Log;
+
+import java.util.Iterator;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.atomic.AtomicLong;
+
+class AudioPlaybackHandler {
+ private static final String TAG = "TTS.AudioPlaybackHandler";
+ private static final boolean DBG = false;
+
+ private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
+
+ private static final int SYNTHESIS_START = 1;
+ private static final int SYNTHESIS_DATA_AVAILABLE = 2;
+ private static final int SYNTHESIS_COMPLETE_DATA_AVAILABLE = 3;
+ private static final int SYNTHESIS_DONE = 4;
+
+ private static final int PLAY_AUDIO = 5;
+ private static final int PLAY_SILENCE = 6;
+
+ private static final int SHUTDOWN = -1;
+
+ private static final int DEFAULT_PRIORITY = 1;
+ private static final int HIGH_PRIORITY = 0;
+
+ private final PriorityBlockingQueue<ListEntry> mQueue =
+ new PriorityBlockingQueue<ListEntry>();
+ private final Thread mHandlerThread;
+
+ private volatile MessageParams mCurrentParams = null;
+ // Used only for book keeping and error detection.
+ private volatile SynthesisMessageParams mLastSynthesisRequest = null;
+ // Used to order incoming messages in our priority queue.
+ private final AtomicLong mSequenceIdCtr = new AtomicLong(0);
+
+
+ AudioPlaybackHandler() {
+ mHandlerThread = new Thread(new MessageLoop(), "TTS.AudioPlaybackThread");
+ }
+
+ public void start() {
+ mHandlerThread.start();
+ }
+
+ /**
+ * Stops all synthesis for a given {@code token}. If the current token
+ * is currently being processed, an effort will be made to stop it but
+ * that is not guaranteed.
+ */
+ synchronized public void stop(MessageParams token) {
+ if (token == null) {
+ return;
+ }
+
+ removeMessages(token);
+
+ if (token.getType() == MessageParams.TYPE_SYNTHESIS) {
+ mQueue.add(new ListEntry(SYNTHESIS_DONE, token, HIGH_PRIORITY));
+ } else {
+ final MessageParams current = getCurrentParams();
+
+ if (current != null) {
+ if (token.getType() == MessageParams.TYPE_AUDIO) {
+ ((AudioMessageParams) current).getPlayer().stop();
+ } else if (token.getType() == MessageParams.TYPE_SILENCE) {
+ ((SilenceMessageParams) current).getConditionVariable().open();
+ }
+ }
+ }
+ }
+
+ synchronized public void removePlaybackItems(String callingApp) {
+ removeMessages(callingApp);
+ stop(getCurrentParams());
+ }
+
+ synchronized public void removeAllItems() {
+ removeAllMessages();
+ stop(getCurrentParams());
+ }
+
+ /**
+ * Shut down the audio playback thread.
+ */
+ synchronized public void quit() {
+ stop(getCurrentParams());
+ mQueue.add(new ListEntry(SHUTDOWN, null, HIGH_PRIORITY));
+ }
+
+ void enqueueSynthesisStart(SynthesisMessageParams token) {
+ mQueue.add(new ListEntry(SYNTHESIS_START, token));
+ }
+
+ void enqueueSynthesisDataAvailable(SynthesisMessageParams token) {
+ mQueue.add(new ListEntry(SYNTHESIS_DATA_AVAILABLE, token));
+ }
+
+ void enqueueSynthesisCompleteDataAvailable(SynthesisMessageParams token) {
+ mQueue.add(new ListEntry(SYNTHESIS_COMPLETE_DATA_AVAILABLE, token));
+ }
+
+ void enqueueSynthesisDone(SynthesisMessageParams token) {
+ mQueue.add(new ListEntry(SYNTHESIS_DONE, token));
+ }
+
+ void enqueueAudio(AudioMessageParams token) {
+ mQueue.add(new ListEntry(PLAY_AUDIO, token));
+ }
+
+ void enqueueSilence(SilenceMessageParams token) {
+ mQueue.add(new ListEntry(PLAY_SILENCE, token));
+ }
+
+ // -----------------------------------------
+ // End of public API methods.
+ // -----------------------------------------
+
+ // -----------------------------------------
+ // Methods for managing the message queue.
+ // -----------------------------------------
+
+ /*
+ * The MessageLoop is a handler like implementation that
+ * processes messages from a priority queue.
+ */
+ private final class MessageLoop implements Runnable {
+ @Override
+ public void run() {
+ while (true) {
+ ListEntry entry = null;
+ try {
+ entry = mQueue.take();
+ } catch (InterruptedException ie) {
+ return;
+ }
+
+ if (entry.mWhat == SHUTDOWN) {
+ if (DBG) Log.d(TAG, "MessageLoop : Shutting down");
+ return;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "MessageLoop : Handling message :" + entry.mWhat
+ + " ,seqId : " + entry.mSequenceId);
+ }
+
+ setCurrentParams(entry.mMessage);
+ handleMessage(entry);
+ setCurrentParams(null);
+ }
+ }
+ }
+
+ /*
+ * Remove all messages from the queue that contain the supplied token.
+ * Note that the Iterator is thread safe, and other methods can safely
+ * continue adding to the queue at this point.
+ */
+ synchronized private void removeMessages(MessageParams token) {
+ if (token == null) {
+ return;
+ }
+
+ Iterator<ListEntry> it = mQueue.iterator();
+
+ while (it.hasNext()) {
+ final ListEntry current = it.next();
+ if (current.mMessage == token) {
+ it.remove();
+ }
+ }
+ }
+
+ /*
+ * Atomically clear the queue of all messages.
+ */
+ synchronized private void removeAllMessages() {
+ mQueue.clear();
+ }
+
+ /*
+ * Remove all messages that originate from a given calling app.
+ */
+ synchronized private void removeMessages(String callingApp) {
+ Iterator<ListEntry> it = mQueue.iterator();
+
+ while (it.hasNext()) {
+ final ListEntry current = it.next();
+ // The null check is to prevent us from removing control messages,
+ // such as a shutdown message.
+ if (current.mMessage != null &&
+ callingApp.equals(current.mMessage.getCallingApp())) {
+ it.remove();
+ }
+ }
+ }
+
+ /*
+ * An element of our priority queue of messages. Each message has a priority,
+ * and a sequence id (defined by the order of enqueue calls). Among messages
+ * with the same priority, messages that were received earlier win out.
+ */
+ private final class ListEntry implements Comparable<ListEntry> {
+ final int mWhat;
+ final MessageParams mMessage;
+ final int mPriority;
+ final long mSequenceId;
+
+ private ListEntry(int what, MessageParams message) {
+ this(what, message, DEFAULT_PRIORITY);
+ }
+
+ private ListEntry(int what, MessageParams message, int priority) {
+ mWhat = what;
+ mMessage = message;
+ mPriority = priority;
+ mSequenceId = mSequenceIdCtr.incrementAndGet();
+ }
+
+ @Override
+ public int compareTo(ListEntry that) {
+ if (that == this) {
+ return 0;
+ }
+
+ // Note that this is always 0, 1 or -1.
+ int priorityDiff = mPriority - that.mPriority;
+ if (priorityDiff == 0) {
+ // The == case cannot occur.
+ return (mSequenceId < that.mSequenceId) ? -1 : 1;
+ }
+
+ return priorityDiff;
+ }
+ }
+
+ private void setCurrentParams(MessageParams p) {
+ mCurrentParams = p;
+ }
+
+ private MessageParams getCurrentParams() {
+ return mCurrentParams;
+ }
+
+ // -----------------------------------------
+ // Methods for dealing with individual messages, the methods
+ // below do the actual work.
+ // -----------------------------------------
+
+ private void handleMessage(ListEntry entry) {
+ final MessageParams msg = entry.mMessage;
+ if (entry.mWhat == SYNTHESIS_START) {
+ handleSynthesisStart(msg);
+ } else if (entry.mWhat == SYNTHESIS_DATA_AVAILABLE) {
+ handleSynthesisDataAvailable(msg);
+ } else if (entry.mWhat == SYNTHESIS_DONE) {
+ handleSynthesisDone(msg);
+ } else if (entry.mWhat == SYNTHESIS_COMPLETE_DATA_AVAILABLE) {
+ handleSynthesisCompleteDataAvailable(msg);
+ } else if (entry.mWhat == PLAY_AUDIO) {
+ handleAudio(msg);
+ } else if (entry.mWhat == PLAY_SILENCE) {
+ handleSilence(msg);
+ }
+ }
+
+ // Currently implemented as blocking the audio playback thread for the
+ // specified duration. If a call to stop() is made, the thread
+ // unblocks.
+ private void handleSilence(MessageParams msg) {
+ if (DBG) Log.d(TAG, "handleSilence()");
+ SilenceMessageParams params = (SilenceMessageParams) msg;
+ if (params.getSilenceDurationMs() > 0) {
+ params.getConditionVariable().block(params.getSilenceDurationMs());
+ }
+ params.getDispatcher().dispatchUtteranceCompleted();
+ if (DBG) Log.d(TAG, "handleSilence() done.");
+ }
+
+ // Plays back audio from a given URI. No TTS engine involvement here.
+ private void handleAudio(MessageParams msg) {
+ if (DBG) Log.d(TAG, "handleAudio()");
+ AudioMessageParams params = (AudioMessageParams) msg;
+ // Note that the BlockingMediaPlayer spawns a separate thread.
+ //
+ // TODO: This can be avoided.
+ params.getPlayer().startAndWait();
+ params.getDispatcher().dispatchUtteranceCompleted();
+ if (DBG) Log.d(TAG, "handleAudio() done.");
+ }
+
+ // Denotes the start of a new synthesis request. We create a new
+ // audio track, and prepare it for incoming data.
+ //
+ // Note that since all TTS synthesis happens on a single thread, we
+ // should ALWAYS see the following order :
+ //
+ // handleSynthesisStart -> handleSynthesisDataAvailable(*) -> handleSynthesisDone
+ // OR
+ // handleSynthesisCompleteDataAvailable.
+ private void handleSynthesisStart(MessageParams msg) {
+ if (DBG) Log.d(TAG, "handleSynthesisStart()");
+ final SynthesisMessageParams param = (SynthesisMessageParams) msg;
+
+ // Oops, looks like the engine forgot to call done(). We go through
+ // extra trouble to clean the data to prevent the AudioTrack resources
+ // from being leaked.
+ if (mLastSynthesisRequest != null) {
+ Log.w(TAG, "Error : Missing call to done() for request : " +
+ mLastSynthesisRequest);
+ handleSynthesisDone(mLastSynthesisRequest);
+ }
+
+ mLastSynthesisRequest = param;
+
+ // Create the audio track.
+ final AudioTrack audioTrack = createStreamingAudioTrack(
+ param.mStreamType, param.mSampleRateInHz, param.mAudioFormat,
+ param.mChannelCount, param.mVolume, param.mPan);
+
+ if (DBG) Log.d(TAG, "Created audio track [" + audioTrack.hashCode() + "]");
+
+ param.setAudioTrack(audioTrack);
+ }
+
+ // More data available to be flushed to the audio track.
+ private void handleSynthesisDataAvailable(MessageParams msg) {
+ final SynthesisMessageParams param = (SynthesisMessageParams) msg;
+ if (param.getAudioTrack() == null) {
+ Log.w(TAG, "Error : null audio track in handleDataAvailable.");
+ return;
+ }
+
+ if (param != mLastSynthesisRequest) {
+ Log.e(TAG, "Call to dataAvailable without done() / start()");
+ return;
+ }
+
+ final AudioTrack audioTrack = param.getAudioTrack();
+ final SynthesisMessageParams.ListEntry bufferCopy = param.getNextBuffer();
+
+ if (bufferCopy == null) {
+ Log.e(TAG, "No buffers available to play.");
+ return;
+ }
+
+ int playState = audioTrack.getPlayState();
+ if (playState == AudioTrack.PLAYSTATE_STOPPED) {
+ if (DBG) Log.d(TAG, "AudioTrack stopped, restarting : " + audioTrack.hashCode());
+ audioTrack.play();
+ }
+ int count = 0;
+ while (count < bufferCopy.mLength) {
+ // Note that we don't take bufferCopy.mOffset into account because
+ // it is guaranteed to be 0.
+ int written = audioTrack.write(bufferCopy.mBytes, count, bufferCopy.mLength);
+ if (written <= 0) {
+ break;
+ }
+ count += written;
+ }
+ }
+
+ private void handleSynthesisDone(MessageParams msg) {
+ final SynthesisMessageParams params = (SynthesisMessageParams) msg;
+ handleSynthesisDone(params);
+ }
+
+ // Flush all remaining data to the audio track, stop it and release
+ // all it's resources.
+ private void handleSynthesisDone(SynthesisMessageParams params) {
+ if (DBG) Log.d(TAG, "handleSynthesisDone()");
+ final AudioTrack audioTrack = params.getAudioTrack();
+
+ try {
+ if (audioTrack != null) {
+ audioTrack.flush();
+ audioTrack.stop();
+ if (DBG) Log.d(TAG, "Releasing audio track [" + audioTrack.hashCode() + "]");
+ audioTrack.release();
+ }
+ } finally {
+ params.setAudioTrack(null);
+ params.getDispatcher().dispatchUtteranceCompleted();
+ mLastSynthesisRequest = null;
+ }
+ }
+
+ private void handleSynthesisCompleteDataAvailable(MessageParams msg) {
+ final SynthesisMessageParams params = (SynthesisMessageParams) msg;
+ if (DBG) Log.d(TAG, "completeAudioAvailable(" + params + ")");
+
+ // Channel config and bytes per frame are checked before
+ // this message is sent.
+ int channelConfig = AudioPlaybackHandler.getChannelConfig(params.mChannelCount);
+ int bytesPerFrame = AudioPlaybackHandler.getBytesPerFrame(params.mAudioFormat);
+
+ SynthesisMessageParams.ListEntry entry = params.getNextBuffer();
+
+ if (entry == null) {
+ Log.w(TAG, "completeDataAvailable : No buffers available to play.");
+ return;
+ }
+
+ final AudioTrack audioTrack = new AudioTrack(params.mStreamType, params.mSampleRateInHz,
+ channelConfig, params.mAudioFormat, entry.mLength, AudioTrack.MODE_STATIC);
+
+ // So that handleDone can access this correctly.
+ params.mAudioTrack = audioTrack;
+
+ try {
+ audioTrack.write(entry.mBytes, entry.mOffset, entry.mLength);
+ setupVolume(audioTrack, params.mVolume, params.mPan);
+ audioTrack.play();
+ blockUntilDone(audioTrack, bytesPerFrame, entry.mLength);
+ if (DBG) Log.d(TAG, "Wrote data to audio track successfully : " + entry.mLength);
+ } catch (IllegalStateException ex) {
+ Log.e(TAG, "Playback error", ex);
+ } finally {
+ handleSynthesisDone(msg);
+ }
+ }
+
+
+ private static void blockUntilDone(AudioTrack audioTrack, int bytesPerFrame, int length) {
+ int lengthInFrames = length / bytesPerFrame;
+ int currentPosition = 0;
+ while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames) {
+ long estimatedTimeMs = ((lengthInFrames - currentPosition) * 1000) /
+ audioTrack.getSampleRate();
+ audioTrack.getPlayState();
+ if (DBG) Log.d(TAG, "About to sleep for : " + estimatedTimeMs + " ms," +
+ " Playback position : " + currentPosition);
+ try {
+ Thread.sleep(estimatedTimeMs);
+ } catch (InterruptedException ie) {
+ break;
+ }
+ }
+ }
+
+ private static AudioTrack createStreamingAudioTrack(int streamType, int sampleRateInHz,
+ int audioFormat, int channelCount, float volume, float pan) {
+ int channelConfig = getChannelConfig(channelCount);
+
+ int minBufferSizeInBytes
+ = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
+ int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes);
+
+ AudioTrack audioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig,
+ audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
+ if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
+ Log.w(TAG, "Unable to create audio track.");
+ audioTrack.release();
+ return null;
+ }
+
+ setupVolume(audioTrack, volume, pan);
+ return audioTrack;
+ }
+
+ static int getChannelConfig(int channelCount) {
+ if (channelCount == 1) {
+ return AudioFormat.CHANNEL_OUT_MONO;
+ } else if (channelCount == 2){
+ return AudioFormat.CHANNEL_OUT_STEREO;
+ }
+
+ return 0;
+ }
+
+ static int getBytesPerFrame(int audioFormat) {
+ if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
+ return 1;
+ } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
+ return 2;
+ }
+
+ return -1;
+ }
+
+ private static void setupVolume(AudioTrack audioTrack, float volume, float pan) {
+ float vol = clip(volume, 0.0f, 1.0f);
+ float panning = clip(pan, -1.0f, 1.0f);
+ float volLeft = vol;
+ float volRight = vol;
+ if (panning > 0.0f) {
+ volLeft *= (1.0f - panning);
+ } else if (panning < 0.0f) {
+ volRight *= (1.0f + panning);
+ }
+ if (DBG) Log.d(TAG, "volLeft=" + volLeft + ",volRight=" + volRight);
+ if (audioTrack.setStereoVolume(volLeft, volRight) != AudioTrack.SUCCESS) {
+ Log.e(TAG, "Failed to set volume");
+ }
+ }
+
+ private static float clip(float value, float min, float max) {
+ return value > max ? max : (value < min ? min : value);
+ }
+
+}
diff --git a/core/java/android/speech/tts/BlockingMediaPlayer.java b/core/java/android/speech/tts/BlockingMediaPlayer.java
new file mode 100644
index 0000000..3cf60dd
--- /dev/null
+++ b/core/java/android/speech/tts/BlockingMediaPlayer.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+
+/**
+ * A media player that allows blocking to wait for it to finish.
+ */
+class BlockingMediaPlayer {
+
+ private static final String TAG = "BlockMediaPlayer";
+
+ private static final String MEDIA_PLAYER_THREAD_NAME = "TTS-MediaPlayer";
+
+ private final Context mContext;
+ private final Uri mUri;
+ private final int mStreamType;
+ private final ConditionVariable mDone;
+ // Only accessed on the Handler thread
+ private MediaPlayer mPlayer;
+ private volatile boolean mFinished;
+
+ /**
+ * Creates a new blocking media player.
+ * Creating a blocking media player is a cheap operation.
+ *
+ * @param context
+ * @param uri
+ * @param streamType
+ */
+ public BlockingMediaPlayer(Context context, Uri uri, int streamType) {
+ mContext = context;
+ mUri = uri;
+ mStreamType = streamType;
+ mDone = new ConditionVariable();
+
+ }
+
+ /**
+ * Starts playback and waits for it to finish.
+ * Can be called from any thread.
+ *
+ * @return {@code true} if the playback finished normally, {@code false} if the playback
+ * failed or {@link #stop} was called before the playback finished.
+ */
+ public boolean startAndWait() {
+ HandlerThread thread = new HandlerThread(MEDIA_PLAYER_THREAD_NAME);
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ mFinished = false;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ startPlaying();
+ }
+ });
+ mDone.block();
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ // No new messages should get posted to the handler thread after this
+ Looper.myLooper().quit();
+ }
+ });
+ return mFinished;
+ }
+
+ /**
+ * Stops playback. Can be called multiple times.
+ * Can be called from any thread.
+ */
+ public void stop() {
+ mDone.open();
+ }
+
+ /**
+ * Starts playback.
+ * Called on the handler thread.
+ */
+ private void startPlaying() {
+ mPlayer = MediaPlayer.create(mContext, mUri);
+ if (mPlayer == null) {
+ Log.w(TAG, "Failed to play " + mUri);
+ mDone.open();
+ return;
+ }
+ try {
+ mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Log.w(TAG, "Audio playback error: " + what + ", " + extra);
+ mDone.open();
+ return true;
+ }
+ });
+ mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ mFinished = true;
+ mDone.open();
+ }
+ });
+ mPlayer.setAudioStreamType(mStreamType);
+ mPlayer.start();
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "MediaPlayer failed", ex);
+ mDone.open();
+ }
+ }
+
+ /**
+ * Stops playback and release the media player.
+ * Called on the handler thread.
+ */
+ private void finish() {
+ try {
+ mPlayer.stop();
+ } catch (IllegalStateException ex) {
+ // Do nothing, the player is already stopped
+ }
+ mPlayer.release();
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
new file mode 100644
index 0000000..4f4b3fb
--- /dev/null
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.media.AudioFormat;
+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;
+
+/**
+ * Speech synthesis request that writes the audio to a WAV file.
+ */
+class FileSynthesisCallback extends AbstractSynthesisCallback {
+
+ private static final String TAG = "FileSynthesisRequest";
+ private static final boolean DBG = false;
+
+ private static final int MAX_AUDIO_BUFFER_SIZE = 8192;
+
+ private static final int WAV_HEADER_LENGTH = 44;
+ 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 boolean mStopped = false;
+ private boolean mDone = false;
+
+ FileSynthesisCallback(File fileName) {
+ mFileName = fileName;
+ }
+
+ @Override
+ void stop() {
+ synchronized (mStateLock) {
+ mStopped = true;
+ cleanUp();
+ }
+ }
+
+ /**
+ * Must be called while holding the monitor on {@link #mStateLock}.
+ */
+ private void cleanUp() {
+ closeFile();
+ if (mFile != null) {
+ mFileName.delete();
+ }
+ }
+
+ /**
+ * Must be called while holding the monitor on {@link #mStateLock}.
+ */
+ private void closeFile() {
+ try {
+ if (mFile != null) {
+ mFile.close();
+ mFile = null;
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
+ }
+ }
+
+ @Override
+ public int getMaxBufferSize() {
+ return MAX_AUDIO_BUFFER_SIZE;
+ }
+
+ @Override
+ boolean isDone() {
+ return mDone;
+ }
+
+ @Override
+ public int start(int sampleRateInHz, int audioFormat, int channelCount) {
+ if (DBG) {
+ Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat
+ + "," + channelCount + ")");
+ }
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mFile != null) {
+ cleanUp();
+ throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
+ }
+ mSampleRateInHz = sampleRateInHz;
+ mAudioFormat = audioFormat;
+ mChannelCount = channelCount;
+ try {
+ mFile = new RandomAccessFile(mFileName, "rw");
+ // Reserve space for WAV header
+ mFile.write(new byte[WAV_HEADER_LENGTH]);
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to open " + mFileName + ": " + ex);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ }
+ }
+
+ @Override
+ public int audioAvailable(byte[] buffer, int offset, int length) {
+ if (DBG) {
+ Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset
+ + "," + length + ")");
+ }
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mFile == null) {
+ Log.e(TAG, "File not open");
+ return TextToSpeech.ERROR;
+ }
+ try {
+ mFile.write(buffer, offset, length);
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ }
+ }
+
+ @Override
+ public int done() {
+ if (DBG) Log.d(TAG, "FileSynthesisRequest.done()");
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mFile == 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(
+ makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
+ closeFile();
+ mDone = true;
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ }
+ }
+
+ @Override
+ public void error() {
+ if (DBG) Log.d(TAG, "FileSynthesisRequest.error()");
+ synchronized (mStateLock) {
+ cleanUp();
+ }
+ }
+
+ @Override
+ public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
+ byte[] buffer, int offset, int length) {
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ }
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(mFileName);
+ out.write(makeWavHeader(sampleRateInHz, audioFormat, channelCount, length));
+ out.write(buffer, offset, length);
+ mDone = true;
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ mFileName.delete();
+ return TextToSpeech.ERROR;
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
+ }
+ }
+ }
+
+ private byte[] 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);
+ int byteRate = sampleRateInHz * sampleSizeInBytes * channelCount;
+ short blockAlign = (short) (sampleSizeInBytes * channelCount);
+ short bitsPerSample = (short) (sampleSizeInBytes * 8);
+
+ byte[] headerBuf = new byte[WAV_HEADER_LENGTH];
+ ByteBuffer header = ByteBuffer.wrap(headerBuf);
+ header.order(ByteOrder.LITTLE_ENDIAN);
+
+ header.put(new byte[]{ 'R', 'I', 'F', 'F' });
+ header.putInt(dataLength + WAV_HEADER_LENGTH - 8); // RIFF chunk size
+ header.put(new byte[]{ 'W', 'A', 'V', 'E' });
+ header.put(new byte[]{ 'f', 'm', 't', ' ' });
+ header.putInt(16); // size of fmt chunk
+ header.putShort(WAV_FORMAT_PCM);
+ header.putShort((short) channelCount);
+ header.putInt(sampleRateInHz);
+ header.putInt(byteRate);
+ header.putShort(blockAlign);
+ header.putShort(bitsPerSample);
+ header.put(new byte[]{ 'd', 'a', 't', 'a' });
+ header.putInt(dataLength);
+
+ return headerBuf;
+ }
+
+}
diff --git a/core/java/android/speech/tts/ITtsCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index c9898eb..40902ae 100755
--- a/core/java/android/speech/tts/ITtsCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.speech.tts;
/**
- * AIDL for the callback from the TTS Service
- * ITtsCallback.java is autogenerated from this.
+ * Interface for callbacks from TextToSpeechService
*
* {@hide}
*/
-oneway interface ITtsCallback {
+oneway interface ITextToSpeechCallback {
void utteranceCompleted(String utteranceId);
}
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
new file mode 100644
index 0000000..ff3fa11
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech.tts;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.speech.tts.ITextToSpeechCallback;
+
+/**
+ * Interface for TextToSpeech to talk to TextToSpeechService.
+ *
+ * {@hide}
+ */
+interface ITextToSpeechService {
+
+ /**
+ * Tells the engine to synthesize some speech and play it back.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param text The text to synthesize.
+ * @param queueMode Determines what to do to requests already in the queue.
+ * @param param Request parameters.
+ */
+ int speak(in String callingApp, in String text, in int queueMode, in Bundle params);
+
+ /**
+ * Tells the engine to synthesize some speech and write it to a file.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param text The text to synthesize.
+ * @param filename The file to write the synthesized audio to.
+ * @param param Request parameters.
+ */
+ int synthesizeToFile(in String callingApp, in String text,
+ in String filename, in Bundle params);
+
+ /**
+ * Plays an existing audio resource.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param audioUri URI for the audio resource (a file or android.resource URI)
+ * @param queueMode Determines what to do to requests already in the queue.
+ * @param param Request parameters.
+ */
+ int playAudio(in String callingApp, in Uri audioUri, in int queueMode, in Bundle params);
+
+ /**
+ * Plays silence.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param duration Number of milliseconds of silence to play.
+ * @param queueMode Determines what to do to requests already in the queue.
+ * @param param Request parameters.
+ */
+ int playSilence(in String callingApp, in long duration, in int queueMode, in Bundle params);
+
+ /**
+ * Checks whether the service is currently playing some audio.
+ */
+ boolean isSpeaking();
+
+ /**
+ * Interrupts the current utterance (if from the given app) and removes any utterances
+ * in the queue that are from the given app.
+ *
+ * @param callingApp Package name of the app whose utterances
+ * should be interrupted and cleared.
+ */
+ int stop(in String callingApp);
+
+ /**
+ * Returns the language, country and variant currently being used by the TTS engine.
+ *
+ * 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[] getLanguage();
+
+ /**
+ * Checks whether the engine supports a given language.
+ *
+ * @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.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ int isLanguageAvailable(in String lang, in String country, in String variant);
+
+ /**
+ * Notifies the engine that it should load a speech synthesis language.
+ *
+ * @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.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ int loadLanguage(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 cb The callback.
+ */
+ void setCallback(in String callingApp, ITextToSpeechCallback cb);
+
+}
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
deleted file mode 100755
index c1051c4..0000000
--- a/core/java/android/speech/tts/ITts.aidl
+++ /dev/null
@@ -1,69 +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.speech.tts;
-
-import android.speech.tts.ITtsCallback;
-
-import android.content.Intent;
-
-/**
- * AIDL for the TTS Service
- * ITts.java is autogenerated from this.
- *
- * {@hide}
- */
-interface ITts {
- int setSpeechRate(in String callingApp, in int speechRate);
-
- int setPitch(in String callingApp, in int pitch);
-
- int speak(in String callingApp, in String text, in int queueMode, in String[] params);
-
- boolean isSpeaking();
-
- int stop(in String callingApp);
-
- void addSpeech(in String callingApp, in String text, in String packageName, in int resId);
-
- void addSpeechFile(in String callingApp, in String text, in String filename);
-
- String[] getLanguage();
-
- int isLanguageAvailable(in String language, in String country, in String variant, in String[] params);
-
- int setLanguage(in String callingApp, in String language, in String country, in String variant);
-
- boolean synthesizeToFile(in String callingApp, in String text, in String[] params, in String outputDirectory);
-
- int playEarcon(in String callingApp, in String earcon, in int queueMode, in String[] params);
-
- void addEarcon(in String callingApp, in String earcon, in String packageName, in int resId);
-
- void addEarconFile(in String callingApp, in String earcon, in String filename);
-
- int registerCallback(in String callingApp, ITtsCallback cb);
-
- int unregisterCallback(in String callingApp, ITtsCallback cb);
-
- int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params);
-
- int setEngineByPackageName(in String enginePackageName);
-
- String getDefaultEngine();
-
- boolean areDefaultsEnforced();
-}
diff --git a/core/java/android/speech/tts/MessageParams.java b/core/java/android/speech/tts/MessageParams.java
new file mode 100644
index 0000000..4c1b6d2
--- /dev/null
+++ b/core/java/android/speech/tts/MessageParams.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+
+abstract class MessageParams {
+ static final int TYPE_SYNTHESIS = 1;
+ static final int TYPE_AUDIO = 2;
+ static final int TYPE_SILENCE = 3;
+
+ private final UtteranceCompletedDispatcher mDispatcher;
+ private final String mCallingApp;
+
+ MessageParams(UtteranceCompletedDispatcher dispatcher, String callingApp) {
+ mDispatcher = dispatcher;
+ mCallingApp = callingApp;
+ }
+
+ UtteranceCompletedDispatcher getDispatcher() {
+ return mDispatcher;
+ }
+
+ String getCallingApp() {
+ return mCallingApp;
+ }
+
+ abstract int getType();
+}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
new file mode 100644
index 0000000..bdaa1b8
--- /dev/null
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.util.Log;
+
+/**
+ * Speech synthesis request that plays the audio as it is received.
+ */
+class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
+
+ private static final String TAG = "PlaybackSynthesisRequest";
+ private static final boolean DBG = false;
+
+ private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
+
+ /**
+ * Audio stream type. Must be one of the STREAM_ contants defined in
+ * {@link android.media.AudioManager}.
+ */
+ private final int mStreamType;
+
+ /**
+ * Volume, in the range [0.0f, 1.0f]. The default value is
+ * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
+ */
+ private final float mVolume;
+
+ /**
+ * Left/right position of the audio, in the range [-1.0f, 1.0f].
+ * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
+ */
+ private final float mPan;
+
+ /**
+ * Guards {@link #mAudioTrackHandler}, {@link #mToken} and {@link #mStopped}.
+ */
+ private final Object mStateLock = new Object();
+
+ // Handler associated with a thread that plays back audio requests.
+ private final AudioPlaybackHandler mAudioTrackHandler;
+ // A request "token", which will be non null after start() or
+ // completeAudioAvailable() have been called.
+ private SynthesisMessageParams mToken = null;
+ // Whether this request has been stopped. This is useful for keeping
+ // track whether stop() has been called before start(). In all other cases,
+ // a non-null value of mToken will provide the same information.
+ private boolean mStopped = false;
+
+ private volatile boolean mDone = false;
+
+ private final UtteranceCompletedDispatcher mDispatcher;
+ private final String mCallingApp;
+
+ PlaybackSynthesisCallback(int streamType, float volume, float pan,
+ AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher dispatcher,
+ String callingApp) {
+ mStreamType = streamType;
+ mVolume = volume;
+ mPan = pan;
+ mAudioTrackHandler = audioTrackHandler;
+ mDispatcher = dispatcher;
+ mCallingApp = callingApp;
+ }
+
+ @Override
+ void stop() {
+ if (DBG) Log.d(TAG, "stop()");
+
+ synchronized (mStateLock) {
+ if (mToken == null || mStopped) {
+ Log.w(TAG, "stop() called twice, before start(), or after done()");
+ return;
+ }
+ mAudioTrackHandler.stop(mToken);
+ mToken = null;
+ mStopped = true;
+ }
+ }
+
+ @Override
+ public int getMaxBufferSize() {
+ // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
+ // a safe buffer size to pass in.
+ return MIN_AUDIO_BUFFER_SIZE;
+ }
+
+ @Override
+ boolean isDone() {
+ return mDone;
+ }
+
+ @Override
+ public int start(int sampleRateInHz, int audioFormat, int channelCount) {
+ if (DBG) {
+ Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat
+ + "," + channelCount + ")");
+ }
+
+ int channelConfig = AudioPlaybackHandler.getChannelConfig(channelCount);
+ if (channelConfig == 0) {
+ Log.e(TAG, "Unsupported number of channels :" + channelCount);
+ return TextToSpeech.ERROR;
+ }
+
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "stop() called before start(), returning.");
+ return TextToSpeech.ERROR;
+ }
+ SynthesisMessageParams params = new SynthesisMessageParams(
+ mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
+ mDispatcher, mCallingApp);
+ mAudioTrackHandler.enqueueSynthesisStart(params);
+
+ mToken = params;
+ }
+
+ return TextToSpeech.SUCCESS;
+ }
+
+
+ @Override
+ public int audioAvailable(byte[] buffer, int offset, int length) {
+ if (DBG) {
+ Log.d(TAG, "audioAvailable(byte[" + buffer.length + "],"
+ + offset + "," + length + ")");
+ }
+ if (length > getMaxBufferSize() || length <= 0) {
+ throw new IllegalArgumentException("buffer is too large or of zero length (" +
+ + length + " bytes)");
+ }
+
+ synchronized (mStateLock) {
+ if (mToken == null) {
+ return TextToSpeech.ERROR;
+ }
+
+ // Sigh, another copy.
+ final byte[] bufferCopy = new byte[length];
+ System.arraycopy(buffer, offset, bufferCopy, 0, length);
+ mToken.addBuffer(bufferCopy);
+ mAudioTrackHandler.enqueueSynthesisDataAvailable(mToken);
+ }
+
+ return TextToSpeech.SUCCESS;
+ }
+
+ @Override
+ public int done() {
+ if (DBG) Log.d(TAG, "done()");
+
+ synchronized (mStateLock) {
+ if (mDone) {
+ Log.w(TAG, "Duplicate call to done()");
+ return TextToSpeech.ERROR;
+ }
+
+ mDone = true;
+
+ if (mToken == null) {
+ return TextToSpeech.ERROR;
+ }
+
+ mAudioTrackHandler.enqueueSynthesisDone(mToken);
+ }
+ return TextToSpeech.SUCCESS;
+ }
+
+ @Override
+ public void error() {
+ if (DBG) Log.d(TAG, "error() [will call stop]");
+ stop();
+ }
+
+ @Override
+ public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
+ byte[] buffer, int offset, int length) {
+ int channelConfig = AudioPlaybackHandler.getChannelConfig(channelCount);
+ if (channelConfig == 0) {
+ Log.e(TAG, "Unsupported number of channels :" + channelCount);
+ return TextToSpeech.ERROR;
+ }
+
+ int bytesPerFrame = AudioPlaybackHandler.getBytesPerFrame(audioFormat);
+ if (bytesPerFrame < 0) {
+ Log.e(TAG, "Unsupported audio format :" + audioFormat);
+ return TextToSpeech.ERROR;
+ }
+
+ synchronized (mStateLock) {
+ if (mStopped) {
+ return TextToSpeech.ERROR;
+ }
+ SynthesisMessageParams params = new SynthesisMessageParams(
+ mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
+ mDispatcher, mCallingApp);
+ params.addBuffer(buffer, offset, length);
+
+ mAudioTrackHandler.enqueueSynthesisCompleteDataAvailable(params);
+ mToken = params;
+ }
+
+ return TextToSpeech.SUCCESS;
+ }
+
+}
diff --git a/core/java/android/speech/tts/SilenceMessageParams.java b/core/java/android/speech/tts/SilenceMessageParams.java
new file mode 100644
index 0000000..7a4ff1c
--- /dev/null
+++ b/core/java/android/speech/tts/SilenceMessageParams.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.os.ConditionVariable;
+import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+
+class SilenceMessageParams extends MessageParams {
+ private final ConditionVariable mCondVar = new ConditionVariable();
+ private final long mSilenceDurationMs;
+
+ SilenceMessageParams(UtteranceCompletedDispatcher dispatcher,
+ String callingApp, long silenceDurationMs) {
+ super(dispatcher, callingApp);
+ mSilenceDurationMs = silenceDurationMs;
+ }
+
+ long getSilenceDurationMs() {
+ return mSilenceDurationMs;
+ }
+
+ @Override
+ int getType() {
+ return TYPE_SILENCE;
+ }
+
+ ConditionVariable getConditionVariable() {
+ return mCondVar;
+ }
+
+}
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
new file mode 100644
index 0000000..1b80e40
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+/**
+ * A callback to return speech data synthesized by a text to speech engine.
+ *
+ * The engine can provide streaming audio by calling
+ * {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally
+ * {@link #done}.
+ *
+ * Alternatively, the engine can provide all the audio at once, by using
+ * {@link #completeAudioAvailable}.
+ *
+ * {@link #error} can be called at any stage in the synthesis process to
+ * indicate that an error has occured, but if the call is made after a call
+ * to {@link #done} or {@link #completeAudioAvailable} it might be discarded.
+ */
+public interface SynthesisCallback {
+ /**
+ * @return the maximum number of bytes that the TTS engine can pass in a single call of
+ * {@link #audioAvailable}. This does not apply to {@link #completeAudioAvailable}.
+ * Calls to {@link #audioAvailable} with data lengths larger than this
+ * value will not succeed.
+ */
+ public int getMaxBufferSize();
+
+ /**
+ * The service should call this when it starts to synthesize audio for this
+ * request.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ *
+ * @param sampleRateInHz Sample rate in HZ of the generated audio.
+ * @param audioFormat Audio format of the generated audio. Must be one of
+ * the ENCODING_ constants defined in {@link android.media.AudioFormat}.
+ * @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public int start(int sampleRateInHz, int audioFormat, int channelCount);
+
+ /**
+ * The service should call this method when synthesized audio is ready for consumption.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ *
+ * @param buffer The generated audio data. This method will not hold on to {@code buffer},
+ * so the caller is free to modify it after this method returns.
+ * @param offset The offset into {@code buffer} where the audio data starts.
+ * @param length The number of bytes of audio data in {@code buffer}. This must be
+ * less than or equal to the return value of {@link #getMaxBufferSize}.
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public int audioAvailable(byte[] buffer, int offset, int length);
+
+ /**
+ * The service can call this method instead of using {@link #start}, {@link #audioAvailable}
+ * and {@link #done} if all the audio data is available in a single buffer.
+ *
+ * @param sampleRateInHz Sample rate in HZ of the generated audio.
+ * @param audioFormat Audio format of the generated audio. Must be one of
+ * the ENCODING_ constants defined in {@link android.media.AudioFormat}.
+ * @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
+ * @param buffer The generated audio data. This method will not hold on to {@code buffer},
+ * so the caller is free to modify it after this method returns.
+ * @param offset The offset into {@code buffer} where the audio data starts.
+ * @param length The number of bytes of audio data in {@code buffer}.
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public int completeAudioAvailable(int sampleRateInHz, int audioFormat,
+ int channelCount, byte[] buffer, int offset, int length);
+
+ /**
+ * The service should call this method when all the synthesized audio for a request has
+ * been passed to {@link #audioAvailable}.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ *
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public int done();
+
+ /**
+ * The service should call this method if the speech synthesis fails.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ */
+ public void error();
+
+} \ No newline at end of file
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
new file mode 100644
index 0000000..51f3d2e
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.media.AudioTrack;
+import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+
+import java.util.LinkedList;
+
+/**
+ * Params required to play back a synthesis request.
+ */
+final class SynthesisMessageParams extends MessageParams {
+ final int mStreamType;
+ final int mSampleRateInHz;
+ final int mAudioFormat;
+ final int mChannelCount;
+ final float mVolume;
+ final float mPan;
+
+ public volatile AudioTrack mAudioTrack;
+
+ private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
+
+ SynthesisMessageParams(int streamType, int sampleRate,
+ int audioFormat, int channelCount,
+ float volume, float pan, UtteranceCompletedDispatcher dispatcher,
+ String callingApp) {
+ super(dispatcher, callingApp);
+
+ mStreamType = streamType;
+ mSampleRateInHz = sampleRate;
+ mAudioFormat = audioFormat;
+ mChannelCount = channelCount;
+ mVolume = volume;
+ mPan = pan;
+
+ // initially null.
+ mAudioTrack = null;
+ }
+
+ @Override
+ int getType() {
+ return TYPE_SYNTHESIS;
+ }
+
+ synchronized void addBuffer(byte[] buffer, int offset, int length) {
+ mDataBufferList.add(new ListEntry(buffer, offset, length));
+ }
+
+ synchronized void addBuffer(byte[] buffer) {
+ mDataBufferList.add(new ListEntry(buffer, 0, buffer.length));
+ }
+
+ synchronized ListEntry getNextBuffer() {
+ return mDataBufferList.poll();
+ }
+
+
+ void setAudioTrack(AudioTrack audioTrack) {
+ mAudioTrack = audioTrack;
+ }
+
+ AudioTrack getAudioTrack() {
+ return mAudioTrack;
+ }
+
+ static final class ListEntry {
+ final byte[] mBytes;
+ final int mOffset;
+ final int mLength;
+
+ ListEntry(byte[] bytes, int offset, int length) {
+ mBytes = bytes;
+ mOffset = offset;
+ mLength = length;
+ }
+ }
+}
+
diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java
new file mode 100644
index 0000000..ef1704c
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisRequest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.os.Bundle;
+
+/**
+ * Contains data required by engines to synthesize speech. This data is :
+ * <ul>
+ * <li>The text to synthesize</li>
+ * <li>The synthesis locale, represented as a language, country and a variant.
+ * The language is an ISO 639-3 letter language code, and the country is an
+ * ISO 3166 alpha 3 code. The variant is not specified.</li>
+ * <li>The synthesis speech rate, with 100 being the normal, and
+ * higher values representing higher speech rates.</li>
+ * <li>The voice pitch, with 100 being the default pitch.</li>
+ * </ul>
+ *
+ * Any additional parameters sent to the text to speech service are passed in
+ * uninterpreted, see the @code{params} argument in {@link TextToSpeech#speak}
+ * and {@link TextToSpeech#synthesizeToFile}.
+ */
+public final class SynthesisRequest {
+ private final String mText;
+ private final Bundle mParams;
+ private String mLanguage;
+ private String mCountry;
+ private String mVariant;
+ private int mSpeechRate;
+ private int mPitch;
+
+ SynthesisRequest(String text, Bundle params) {
+ mText = text;
+ // Makes a copy of params.
+ mParams = new Bundle(params);
+ }
+
+ /**
+ * Gets the text which should be synthesized.
+ */
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Gets the ISO 3-letter language code for the language to use.
+ */
+ public String getLanguage() {
+ return mLanguage;
+ }
+
+ /**
+ * Gets the ISO 3-letter country code for the language to use.
+ */
+ public String getCountry() {
+ return mCountry;
+ }
+
+ /**
+ * Gets the language variant to use.
+ */
+ public String getVariant() {
+ return mVariant;
+ }
+
+ /**
+ * Gets the speech rate to use. The normal rate is 100.
+ */
+ public int getSpeechRate() {
+ return mSpeechRate;
+ }
+
+ /**
+ * Gets the pitch to use. The normal pitch is 100.
+ */
+ public int getPitch() {
+ return mPitch;
+ }
+
+ /**
+ * Gets the additional params, if any.
+ */
+ public Bundle getParams() {
+ return mParams;
+ }
+
+ /**
+ * Sets the locale for the request.
+ */
+ void setLanguage(String language, String country, String variant) {
+ mLanguage = language;
+ mCountry = country;
+ mVariant = variant;
+ }
+
+ /**
+ * Sets the speech rate.
+ */
+ void setSpeechRate(int speechRate) {
+ mSpeechRate = speechRate;
+ }
+
+ /**
+ * Sets the pitch.
+ */
+ void setPitch(int pitch) {
+ mPitch = pitch;
+ }
+}
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 186af70..23fd96f 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 Google Inc.
+ * 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
@@ -15,22 +15,26 @@
*/
package android.speech.tts;
-import android.speech.tts.ITts;
-import android.speech.tts.ITtsCallback;
-
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
/**
*
@@ -44,41 +48,50 @@ import java.util.Locale;
*/
public class TextToSpeech {
+ private static final String TAG = "TextToSpeech";
+
/**
* Denotes a successful operation.
*/
- public static final int SUCCESS = 0;
+ public static final int SUCCESS = 0;
/**
* Denotes a generic operation failure.
*/
- public static final int ERROR = -1;
+ public static final int ERROR = -1;
/**
* Queue mode where all entries in the playback queue (media to be played
* and text to be synthesized) are dropped and replaced by the new entry.
+ * Queues are flushed with respect to a given calling app. Entries in the queue
+ * from other callees are not discarded.
*/
public static final int QUEUE_FLUSH = 0;
/**
* Queue mode where the new entry is added at the end of the playback queue.
*/
public static final int QUEUE_ADD = 1;
-
+ /**
+ * Queue mode where the entire playback queue is purged. This is different
+ * from {@link #QUEUE_FLUSH} in that all entries are purged, not just entries
+ * from a given caller.
+ *
+ * @hide
+ */
+ static final int QUEUE_DESTROY = 2;
/**
* Denotes the language is available exactly as specified by the locale.
*/
public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
-
/**
- * Denotes the language is available for the language and country specified
+ * Denotes the language is available for the language and country specified
* by the locale, but not the variant.
*/
public static final int LANG_COUNTRY_AVAILABLE = 1;
-
/**
- * Denotes the language is available for the language by the locale,
+ * Denotes the language is available for the language by the locale,
* but not the country and variant.
*/
public static final int LANG_AVAILABLE = 0;
@@ -93,7 +106,6 @@ public class TextToSpeech {
*/
public static final int LANG_NOT_SUPPORTED = -2;
-
/**
* Broadcast Action: The TextToSpeech synthesizer has completed processing
* of all the text in the speech queue.
@@ -102,7 +114,6 @@ public class TextToSpeech {
public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
"android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
-
/**
* Interface definition of a callback to be invoked indicating the completion of the
* TextToSpeech engine initialization.
@@ -110,103 +121,119 @@ public class TextToSpeech {
public interface OnInitListener {
/**
* Called to signal the completion of the TextToSpeech engine initialization.
+ *
* @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
*/
public void onInit(int status);
}
/**
- * Interface definition of a callback to be invoked indicating the TextToSpeech engine has
- * completed synthesizing an utterance with an utterance ID set.
- *
+ * 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}).
*/
public interface OnUtteranceCompletedListener {
/**
- * Called to signal the completion of the synthesis of the utterance that was identified
- * with the string parameter. This identifier is the one originally passed in the
- * parameter hashmap of the synthesis request in
- * {@link TextToSpeech#speak(String, int, HashMap)} or
- * {@link TextToSpeech#synthesizeToFile(String, HashMap, String)} with the
- * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID} key.
+ * Called when an utterance has been synthesized.
+ *
* @param utteranceId the identifier of the utterance.
*/
public void onUtteranceCompleted(String utteranceId);
}
-
/**
- * Internal constants for the TextToSpeech functionality
- *
+ * Constants and parameter names for controlling text-to-speech.
*/
public class Engine {
- // default values for a TTS engine when settings are not found in the provider
+
/**
- * {@hide}
+ * Default speech rate.
+ * @hide
*/
- public static final int DEFAULT_RATE = 100; // 1x
+ public static final int DEFAULT_RATE = 100;
+
/**
- * {@hide}
+ * Default pitch.
+ * @hide
*/
- public static final int DEFAULT_PITCH = 100;// 1x
+ public static final int DEFAULT_PITCH = 100;
+
/**
- * {@hide}
+ * Default volume.
+ * @hide
*/
public static final float DEFAULT_VOLUME = 1.0f;
+
/**
- * {@hide}
- */
- protected static final String DEFAULT_VOLUME_STRING = "1.0";
- /**
- * {@hide}
+ * Default pan (centered).
+ * @hide
*/
public static final float DEFAULT_PAN = 0.0f;
- /**
- * {@hide}
- */
- protected static final String DEFAULT_PAN_STRING = "0.0";
/**
- * {@hide}
+ * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
+ * @hide
*/
public static final int USE_DEFAULTS = 0; // false
+
/**
- * {@hide}
+ * Package name of the default TTS engine.
+ *
+ * @hide
+ * @deprecated No longer in use, the default engine is determined by
+ * the sort order defined in {@link EngineInfoComparator}. Note that
+ * this doesn't "break" anything because there is no guarantee that
+ * the engine specified below is installed on a given build, let
+ * alone be the default.
*/
- public static final String DEFAULT_SYNTH = "com.svox.pico";
+ @Deprecated
+ public static final String DEFAULT_ENGINE = "com.svox.pico";
- // default values for rendering
/**
* Default audio stream used when playing synthesized speech.
*/
public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
- // return codes for a TTS engine's check data activity
/**
* Indicates success when checking the installation status of the resources used by the
* TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
public static final int CHECK_VOICE_DATA_PASS = 1;
+
/**
* Indicates failure when checking the installation status of the resources used by the
* TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
public static final int CHECK_VOICE_DATA_FAIL = 0;
+
/**
* Indicates erroneous data when checking the installation status of the resources used by
* the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
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.
*/
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.
*/
public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
+ /**
+ * Intent for starting a TTS service. Services that handle this intent must
+ * extend {@link TextToSpeechService}. Normal applications should not use this intent
+ * directly, instead they should talk to the TTS service using the the methods in this
+ * class.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String INTENT_ACTION_TTS_SERVICE =
+ "android.intent.action.TTS_SERVICE";
+
// intents to ask engine to install data or check its data
/**
* Activity Action: Triggers the platform TextToSpeech engine to
@@ -229,6 +256,7 @@ public class TextToSpeech {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TTS_DATA_INSTALLED =
"android.speech.tts.engine.TTS_DATA_INSTALLED";
+
/**
* Activity Action: Starts the activity from the platform TextToSpeech
* engine to verify the proper installation and availability of the
@@ -256,23 +284,36 @@ public class TextToSpeech {
public static final String ACTION_CHECK_TTS_DATA =
"android.speech.tts.engine.CHECK_TTS_DATA";
+ /**
+ * Activity intent for getting some sample text to use for demonstrating TTS.
+ *
+ * @hide This intent was used by engines written against the old API.
+ * Not sure if it should be exposed.
+ */
+ @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.
*/
public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+
/**
* 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";
+
/**
* Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
* the TextToSpeech engine returns an ArrayList<String> of all the available voices.
@@ -280,6 +321,7 @@ public class TextToSpeech {
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
*/
public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
+
/**
* Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
* the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
@@ -287,6 +329,7 @@ public class TextToSpeech {
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
*/
public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
+
/**
* 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
@@ -309,137 +352,106 @@ public class TextToSpeech {
// keys for the parameters passed with speak commands. Hidden keys are used internally
// to maintain engine state for each TextToSpeech instance.
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_RATE = "rate";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_LANGUAGE = "language";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_COUNTRY = "country";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_VARIANT = "variant";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_ENGINE = "engine";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_PITCH = "pitch";
+
/**
* Parameter key to specify the audio stream type to be used when speaking text
- * or playing back a file.
+ * or playing back a file. The value should be one of the STREAM_ constants
+ * defined in {@link AudioManager}.
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
*/
public static final String KEY_PARAM_STREAM = "streamType";
+
/**
* Parameter key to identify an utterance in the
* {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
* spoken, a file has been played back or a silence duration has elapsed.
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
* @see TextToSpeech#synthesizeToFile(String, HashMap, String)
*/
public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
+
/**
* Parameter key to specify the speech volume relative to the current stream type
* volume used when speaking text. Volume is specified as a float ranging from 0 to 1
* where 0 is silence, and 1 is the maximum volume (the default behavior).
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
*/
public static final String KEY_PARAM_VOLUME = "volume";
+
/**
* Parameter key to specify how the speech is panned from left to right when speaking text.
* Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
* 0 to center (the default behavior), and +1 to hard-right.
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
*/
public static final String KEY_PARAM_PAN = "pan";
- // key positions in the array of cached parameters
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_RATE = 0;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_LANGUAGE = 2;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_COUNTRY = 4;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_VARIANT = 6;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_STREAM = 8;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_UTTERANCE_ID = 10;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_ENGINE = 12;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_PITCH = 14;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_VOLUME = 16;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_PAN = 18;
-
-
- /**
- * {@hide}
- * Total number of cached speech parameters.
- * This number should be equal to (max param position/2) + 1.
- */
- protected static final int NB_CACHED_PARAMS = 10;
}
- /**
- * Connection needed for the TTS.
- */
- private ServiceConnection mServiceConnection;
-
- private ITts mITts = null;
- private ITtsCallback mITtscallback = null;
- private Context mContext = null;
- private String mPackageName = "";
- private OnInitListener mInitListener = null;
- private boolean mStarted = false;
+ private final Context mContext;
+ private Connection mServiceConnection;
+ private OnInitListener mInitListener;
private final Object mStartLock = new Object();
+
+ private String mRequestedEngine;
+ private final Map<String, Uri> mEarcons;
+ private final Map<String, Uri> mUtterances;
+ private final Bundle mParams = new Bundle();
+ private final TtsEngines mEnginesHelper;
+ private String mCurrentEngine = null;
+
/**
- * Used to store the cached parameters sent along with each synthesis request to the
- * TTS service.
+ * The constructor for the TextToSpeech class, using the default TTS engine.
+ * This will also initialize the associated TextToSpeech engine if it isn't already running.
+ *
+ * @param context
+ * The context this instance is running in.
+ * @param listener
+ * The {@link TextToSpeech.OnInitListener} that will be called when the
+ * TextToSpeech engine has initialized.
*/
- private String[] mCachedParams;
+ public TextToSpeech(Context context, OnInitListener listener) {
+ this(context, listener, null);
+ }
/**
- * The constructor for the TextToSpeech class.
+ * The constructor for the TextToSpeech class, using the given TTS engine.
* This will also initialize the associated TextToSpeech engine if it isn't already running.
*
* @param context
@@ -447,86 +459,97 @@ public class TextToSpeech {
* @param listener
* The {@link TextToSpeech.OnInitListener} that will be called when the
* TextToSpeech engine has initialized.
+ * @param engine Package name of the TTS engine to use.
*/
- public TextToSpeech(Context context, OnInitListener listener) {
+ public TextToSpeech(Context context, OnInitListener listener, String engine) {
mContext = context;
- mPackageName = mContext.getPackageName();
mInitListener = listener;
+ mRequestedEngine = engine;
- mCachedParams = new String[2*Engine.NB_CACHED_PARAMS]; // store key and value
- mCachedParams[Engine.PARAM_POSITION_RATE] = Engine.KEY_PARAM_RATE;
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE] = Engine.KEY_PARAM_LANGUAGE;
- mCachedParams[Engine.PARAM_POSITION_COUNTRY] = Engine.KEY_PARAM_COUNTRY;
- mCachedParams[Engine.PARAM_POSITION_VARIANT] = Engine.KEY_PARAM_VARIANT;
- mCachedParams[Engine.PARAM_POSITION_STREAM] = Engine.KEY_PARAM_STREAM;
- mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID] = Engine.KEY_PARAM_UTTERANCE_ID;
- mCachedParams[Engine.PARAM_POSITION_ENGINE] = Engine.KEY_PARAM_ENGINE;
- mCachedParams[Engine.PARAM_POSITION_PITCH] = Engine.KEY_PARAM_PITCH;
- mCachedParams[Engine.PARAM_POSITION_VOLUME] = Engine.KEY_PARAM_VOLUME;
- mCachedParams[Engine.PARAM_POSITION_PAN] = Engine.KEY_PARAM_PAN;
-
- // Leave all defaults that are shown in Settings uninitialized/at the default
- // so that the values set in Settings will take effect if the application does
- // not try to change these settings itself.
- mCachedParams[Engine.PARAM_POSITION_RATE + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
- String.valueOf(Engine.DEFAULT_STREAM);
- mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = "100";
- mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING;
- mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING;
+ mEarcons = new HashMap<String, Uri>();
+ mUtterances = new HashMap<String, Uri>();
+ mEnginesHelper = new TtsEngines(mContext);
initTts();
}
+ private String getPackageName() {
+ return mContext.getPackageName();
+ }
- private void initTts() {
- mStarted = false;
-
- // Initialize the TTS, run the callback after the binding is successful
- mServiceConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized(mStartLock) {
- mITts = ITts.Stub.asInterface(service);
- mStarted = true;
- // Cache the default engine and current language
- setEngineByPackageName(getDefaultEngine());
- setLanguage(getLanguage());
- if (mInitListener != null) {
- // TODO manage failures and missing resources
- mInitListener.onInit(SUCCESS);
- }
- }
+ private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
+ return runAction(action, errorResult, method, false);
+ }
+
+ private <R> R runAction(Action<R> action, R errorResult, String method) {
+ return runAction(action, errorResult, method, true);
+ }
+
+ private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+ synchronized (mStartLock) {
+ if (mServiceConnection == null) {
+ Log.w(TAG, method + " failed: not bound to TTS engine");
+ return errorResult;
}
+ return mServiceConnection.runAction(action, errorResult, method, reconnect);
+ }
+ }
- public void onServiceDisconnected(ComponentName name) {
- synchronized(mStartLock) {
- mITts = null;
- mInitListener = null;
- mStarted = false;
- }
+ private int initTts() {
+ String defaultEngine = getDefaultEngine();
+ String engine = defaultEngine;
+ if (!areDefaultsEnforced() && !TextUtils.isEmpty(mRequestedEngine)
+ && mEnginesHelper.isEngineEnabled(engine)) {
+ engine = mRequestedEngine;
+ }
+
+ // Try requested engine
+ if (connectToEngine(engine)) {
+ return SUCCESS;
+ }
+
+ // Fall back to user's default engine if different from the already tested one
+ if (!engine.equals(defaultEngine)) {
+ if (connectToEngine(defaultEngine)) {
+ return SUCCESS;
}
- };
+ }
- Intent intent = new Intent("android.intent.action.START_TTS_SERVICE");
- intent.addCategory("android.intent.category.TTS");
- boolean bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
- if (!bound) {
- Log.e("TextToSpeech.java", "initTts() failed to bind to service");
- if (mInitListener != null) {
- mInitListener.onInit(ERROR);
+ final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
+ // Fall back to the hardcoded default if different from the two above
+ if (!defaultEngine.equals(highestRanked)
+ && !engine.equals(highestRanked)) {
+ if (connectToEngine(highestRanked)) {
+ return SUCCESS;
}
+ }
+
+ return ERROR;
+ }
+
+ private boolean connectToEngine(String engine) {
+ Connection connection = new Connection();
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ intent.setPackage(engine);
+ boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ if (!bound) {
+ Log.e(TAG, "Failed to bind to " + engine);
+ dispatchOnInit(ERROR);
+ return false;
} else {
- // initialization listener will be called inside ServiceConnection
- Log.i("TextToSpeech.java", "initTts() successfully bound to service");
+ mCurrentEngine = engine;
+ return true;
}
- // TODO handle plugin failures
}
+ private void dispatchOnInit(int result) {
+ synchronized (mStartLock) {
+ if (mInitListener != null) {
+ mInitListener.onInit(result);
+ mInitListener = null;
+ }
+ }
+ }
/**
* Releases the resources used by the TextToSpeech engine.
@@ -534,15 +557,17 @@ public class TextToSpeech {
* so the TextToSpeech engine can be cleanly stopped.
*/
public void shutdown() {
- try {
- mContext.unbindService(mServiceConnection);
- } catch (IllegalArgumentException e) {
- // Do nothing and fail silently since an error here indicates that
- // binding never succeeded in the first place.
- }
+ runActionNoReconnect(new Action<Void>() {
+ @Override
+ public Void run(ITextToSpeechService service) throws RemoteException {
+ service.setCallback(getPackageName(), null);
+ service.stop(getPackageName());
+ mServiceConnection.disconnect();
+ return null;
+ }
+ }, null, "shutdown");
}
-
/**
* Adds a mapping between a string of text and a sound resource in a
* package. After a call to this method, subsequent calls to
@@ -571,37 +596,12 @@ public class TextToSpeech {
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
public int addSpeech(String text, String packagename, int resourceId) {
- synchronized(mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addSpeech(mPackageName, text, packagename, resourceId);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ synchronized (mStartLock) {
+ mUtterances.put(text, makeResourceUri(packagename, resourceId));
+ return SUCCESS;
}
}
-
/**
* Adds a mapping between a string of text and a sound file. Using this, it
* is possible to add custom pronounciations for a string of text.
@@ -619,32 +619,8 @@ public class TextToSpeech {
*/
public int addSpeech(String text, String filename) {
synchronized (mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addSpeechFile(mPackageName, text, filename);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ mUtterances.put(text, Uri.parse(filename));
+ return SUCCESS;
}
}
@@ -676,36 +652,11 @@ public class TextToSpeech {
*/
public int addEarcon(String earcon, String packagename, int resourceId) {
synchronized(mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addEarcon(mPackageName, earcon, packagename, resourceId);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
+ return SUCCESS;
}
}
-
/**
* Adds a mapping between a string of text and a sound file.
* Use this to add custom earcons.
@@ -722,403 +673,211 @@ public class TextToSpeech {
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
public int addEarcon(String earcon, String filename) {
- synchronized (mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addEarconFile(mPackageName, earcon, filename);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ synchronized(mStartLock) {
+ mEarcons.put(earcon, Uri.parse(filename));
+ return SUCCESS;
}
}
+ private Uri makeResourceUri(String packageName, int resourceId) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .encodedAuthority(packageName)
+ .appendEncodedPath(String.valueOf(resourceId))
+ .build();
+ }
/**
* Speaks the string using the specified queuing strategy and speech
* parameters.
*
- * @param text
- * The string of text to be spoken.
- * @param queueMode
- * The queuing strategy to use.
- * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
- * {@link Engine#KEY_PARAM_STREAM} or
- * {@link Engine#KEY_PARAM_UTTERANCE_ID}.
+ * @param text The string of text to be spoken.
+ * @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:
+ * {@link Engine#KEY_PARAM_STREAM},
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID},
+ * {@link Engine#KEY_PARAM_VOLUME},
+ * {@link Engine#KEY_PARAM_PAN}.
+ * Engine specific parameters may be passed in but the parameter keys
+ * must be prefixed by the name of the engine they are intended for. For example
+ * 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 Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int speak(String text, int queueMode, HashMap<String,String> params)
- {
- synchronized (mStartLock) {
- int result = ERROR;
- Log.i("TextToSpeech.java - speak", "speak text of length " + text.length());
- if (!mStarted) {
- Log.e("TextToSpeech.java - speak", "service isn't started");
- return result;
- }
- try {
- if ((params != null) && (!params.isEmpty())) {
- setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM);
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
- setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE);
- setCachedParam(params, Engine.KEY_PARAM_VOLUME, Engine.PARAM_POSITION_VOLUME);
- setCachedParam(params, Engine.KEY_PARAM_PAN, Engine.PARAM_POSITION_PAN);
+ public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ Uri utteranceUri = mUtterances.get(text);
+ if (utteranceUri != null) {
+ return service.playAudio(getPackageName(), utteranceUri, queueMode,
+ getParams(params));
+ } else {
+ return service.speak(getPackageName(), text, queueMode, getParams(params));
}
- result = mITts.speak(mPackageName, text, queueMode, mCachedParams);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - speak", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - speak", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - speak", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
}
- }
+ }, ERROR, "speak");
}
-
/**
* 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)}.
*
- * @param earcon
- * The earcon that should be played
- * @param queueMode
- * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
- * {@link Engine#KEY_PARAM_STREAM} or
+ * @param earcon The earcon that should be played
+ * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+ * @param params Parameters for the request. Can be null.
+ * Supported parameter names:
+ * {@link Engine#KEY_PARAM_STREAM},
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
+ * Engine specific parameters may be passed in but the parameter keys
+ * must be prefixed by the name of the engine they are intended for. For example
+ * 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 Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int playEarcon(String earcon, int queueMode,
- HashMap<String,String> params) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- if ((params != null) && (!params.isEmpty())) {
- String extra = params.get(Engine.KEY_PARAM_STREAM);
- if (extra != null) {
- mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = extra;
- }
- setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM);
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
+ public int playEarcon(final String earcon, final int queueMode,
+ final HashMap<String, String> params) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ Uri earconUri = mEarcons.get(earcon);
+ if (earconUri == null) {
+ return ERROR;
}
- result = mITts.playEarcon(mPackageName, earcon, queueMode, null);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playEarcon", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playEarcon", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playEarcon", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
+ return service.playAudio(getPackageName(), earconUri, queueMode,
+ getParams(params));
}
- }
+ }, ERROR, "playEarcon");
}
/**
* Plays silence for the specified amount of time using the specified
* queue mode.
*
- * @param durationInMs
- * A long that indicates how long the silence should last.
- * @param queueMode
- * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
+ * @param durationInMs The duration of the silence.
+ * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+ * @param params Parameters for the request. Can be null.
+ * Supported parameter names:
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
+ * Engine specific parameters may be passed in but the parameter keys
+ * must be prefixed by the name of the engine they are intended for. For example
+ * 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 Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int playSilence(long durationInMs, int queueMode, HashMap<String,String> params) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
+ public int playSilence(final long durationInMs, final int queueMode,
+ final HashMap<String, String> params) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ return service.playSilence(getPackageName(), durationInMs, queueMode,
+ getParams(params));
}
- try {
- if ((params != null) && (!params.isEmpty())) {
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
- }
- result = mITts.playSilence(mPackageName, durationInMs, queueMode, mCachedParams);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playSilence", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playSilence", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playSilence", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
- }
- }
+ }, ERROR, "playSilence");
}
-
/**
- * Returns whether or not the TextToSpeech engine is busy speaking.
+ * Checks whether the TTS engine is busy speaking.
*
- * @return Whether or not the TextToSpeech engine is busy speaking.
+ * @return {@code true} if the TTS engine is speaking.
*/
public boolean isSpeaking() {
- synchronized (mStartLock) {
- if (!mStarted) {
- return false;
+ return runAction(new Action<Boolean>() {
+ @Override
+ public Boolean run(ITextToSpeechService service) throws RemoteException {
+ return service.isSpeaking();
}
- try {
- return mITts.isSpeaking();
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isSpeaking", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isSpeaking", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isSpeaking", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return false;
- }
+ }, false, "isSpeaking");
}
-
/**
* Interrupts the current utterance (whether played or rendered to file) and discards other
* utterances in the queue.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int stop() {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- result = mITts.stop(mPackageName);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - stop", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - stop", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - stop", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ return service.stop(getPackageName());
}
- }
+ }, ERROR, "stop");
}
-
/**
- * Sets the speech rate for the TextToSpeech engine.
+ * Sets the speech rate.
*
* This has no effect on any pre-recorded speech.
*
- * @param speechRate
- * The speech rate for the TextToSpeech engine. 1 is the normal speed,
- * lower values slow down the speech (0.5 is half the normal speech rate),
- * greater values accelerate it (2 is twice the normal speech rate).
+ * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
+ * lower values slow down the speech ({@code 0.5} is half the normal speech rate),
+ * greater values accelerate it ({@code 2.0} is twice the normal speech rate).
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int setSpeechRate(float speechRate) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- if (speechRate > 0) {
- int rate = (int)(speechRate*100);
- mCachedParams[Engine.PARAM_POSITION_RATE + 1] = String.valueOf(rate);
- // the rate is not set here, instead it is cached so it will be associated
- // with all upcoming utterances.
- if (speechRate > 0.0f) {
- result = SUCCESS;
- } else {
- result = ERROR;
- }
+ if (speechRate > 0.0f) {
+ int intRate = (int)(speechRate * 100);
+ if (intRate > 0) {
+ synchronized (mStartLock) {
+ mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
}
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setSpeechRate", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setSpeechRate", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ return SUCCESS;
}
}
+ return ERROR;
}
-
/**
* Sets the speech pitch for the TextToSpeech engine.
*
* This has no effect on any pre-recorded speech.
*
- * @param pitch
- * The pitch for the TextToSpeech engine. 1 is the normal pitch,
+ * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
* lower values lower the tone of the synthesized voice,
* greater values increase it.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int setPitch(float pitch) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- // the pitch is not set here, instead it is cached so it will be associated
- // with all upcoming utterances.
- if (pitch > 0) {
- int p = (int)(pitch*100);
- mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = String.valueOf(p);
- result = SUCCESS;
+ if (pitch > 0.0f) {
+ int intPitch = (int)(pitch * 100);
+ if (intPitch > 0) {
+ synchronized (mStartLock) {
+ mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
}
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setPitch", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setPitch", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ return SUCCESS;
}
}
+ return ERROR;
}
-
/**
- * Sets the language for the TextToSpeech engine.
- * The TextToSpeech engine will try to use the closest match to the specified
+ * 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
* will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
* before choosing the language to use for the next utterances.
*
- * @param loc
- * The locale describing the language to be used.
+ * @param loc The locale describing the language to be used.
*
- * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
+ * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
* {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
* {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
*/
- public int setLanguage(Locale loc) {
- synchronized (mStartLock) {
- int result = LANG_NOT_SUPPORTED;
- if (!mStarted) {
- return result;
- }
- if (loc == null) {
- return result;
- }
- try {
+ public int setLanguage(final Locale loc) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ if (loc == null) {
+ return LANG_NOT_SUPPORTED;
+ }
String language = loc.getISO3Language();
String country = loc.getISO3Country();
String variant = loc.getVariant();
@@ -1126,386 +885,306 @@ public class TextToSpeech {
// 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.
- result = mITts.isLanguageAvailable(language, country, variant, mCachedParams);
+ int result = service.loadLanguage(language, country, variant);
if (result >= LANG_AVAILABLE){
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = language;
- if (result >= LANG_COUNTRY_AVAILABLE){
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = country;
- } else {
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = "";
- }
- if (result >= LANG_COUNTRY_VAR_AVAILABLE){
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = variant;
- } else {
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = "";
+ if (result < LANG_COUNTRY_VAR_AVAILABLE) {
+ variant = "";
+ if (result < LANG_COUNTRY_AVAILABLE) {
+ country = "";
+ }
}
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
}
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setLanguage", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setLanguage", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setLanguage", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
return result;
}
- }
+ }, LANG_NOT_SUPPORTED, "setLanguage");
}
-
/**
* Returns a Locale instance describing the language currently being used by the TextToSpeech
* engine.
+ *
* @return language, country (if any) and variant (if any) used by the engine stored in a Locale
- * instance, or null is the TextToSpeech engine has failed.
+ * instance, or {@code null} on error.
*/
public Locale getLanguage() {
- synchronized (mStartLock) {
- if (!mStarted) {
- return null;
- }
- try {
- // Only do a call to the native synth if there is nothing in the cached params
- if (mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1].length() < 1){
- String[] locStrings = mITts.getLanguage();
- if ((locStrings != null) && (locStrings.length == 3)) {
- return new Locale(locStrings[0], locStrings[1], locStrings[2]);
- } else {
- return null;
- }
- } else {
- return new Locale(mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1],
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1],
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1]);
+ 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]);
}
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - getLanguage", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - getLanguage", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - getLanguage", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
+ return null;
}
- return null;
- }
+ }, null, "getLanguage");
}
/**
* Checks if the specified language as represented by the Locale is available and supported.
*
- * @param loc
- * The Locale describing the language to be used.
+ * @param loc The Locale describing the language to be used.
*
- * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
+ * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
* {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
* {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
*/
- public int isLanguageAvailable(Locale loc) {
- synchronized (mStartLock) {
- int result = LANG_NOT_SUPPORTED;
- if (!mStarted) {
- return result;
+ public int isLanguageAvailable(final Locale loc) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ return service.isLanguageAvailable(loc.getISO3Language(),
+ loc.getISO3Country(), loc.getVariant());
}
- try {
- result = mITts.isLanguageAvailable(loc.getISO3Language(),
- loc.getISO3Country(), loc.getVariant(), mCachedParams);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isLanguageAvailable", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isLanguageAvailable", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isLanguageAvailable", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
- }
- }
+ }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
}
-
/**
* Synthesizes the given text to a file using the specified parameters.
*
- * @param text
- * The String of text that should be synthesized
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
+ * @param text Thetext that should be synthesized
+ * @param params Parameters for the request. Can be null.
+ * Supported parameter names:
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
- * @param filename
- * The string that gives the full output filename; it should be
+ * Engine specific parameters may be passed in but the parameter keys
+ * must be prefixed by the name of the engine they are intended for. For example
+ * 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.
+ * @param filename Absolute file filename to write the generated audio data to.It should be
* something like "/sdcard/myappsounds/mysound.wav".
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int synthesizeToFile(String text, HashMap<String,String> params,
- String filename) {
- Log.i("TextToSpeech.java", "synthesizeToFile()");
- synchronized (mStartLock) {
- int result = ERROR;
- Log.i("TextToSpeech.java - synthesizeToFile", "synthesizeToFile text of length "
- + text.length());
- if (!mStarted) {
- Log.e("TextToSpeech.java - synthesizeToFile", "service isn't started");
- return result;
+ 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(getPackageName(), text, filename,
+ getParams(params));
}
- try {
- if ((params != null) && (!params.isEmpty())) {
- // no need to read the stream type here
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
- setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE);
+ }, ERROR, "synthesizeToFile");
+ }
+
+ private Bundle getParams(HashMap<String, String> params) {
+ if (params != null && !params.isEmpty()) {
+ Bundle bundle = new Bundle(mParams);
+ copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
+ copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
+ copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
+ copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
+
+ // Copy over all parameters that start with the name of the
+ // engine that we are currently connected to. The engine is
+ // free to interpret them as it chooses.
+ if (!TextUtils.isEmpty(mCurrentEngine)) {
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+ final String key = entry.getKey();
+ if (key != null && key.startsWith(mCurrentEngine)) {
+ bundle.putString(key, entry.getValue());
+ }
}
- result = mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename) ?
- SUCCESS : ERROR;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - synthesizeToFile", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - synthesizeToFile", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - synthesizeToFile", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
}
+
+ return bundle;
+ } else {
+ return mParams;
}
}
+ private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
+ String value = params.get(key);
+ if (value != null) {
+ bundle.putString(key, value);
+ }
+ }
- /**
- * Convenience method to reset the cached parameters to the current default values
- * if they are not persistent between calls to the service.
- */
- private void resetCachedParams() {
- mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
- String.valueOf(Engine.DEFAULT_STREAM);
- mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID+ 1] = "";
- mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING;
- mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING;
+ private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
+ String valueString = params.get(key);
+ if (!TextUtils.isEmpty(valueString)) {
+ try {
+ int value = Integer.parseInt(valueString);
+ bundle.putInt(key, value);
+ } catch (NumberFormatException ex) {
+ // don't set the value in the bundle
+ }
+ }
}
- /**
- * Convenience method to save a parameter in the cached parameter array, at the given index,
- * for a property saved in the given hashmap.
- */
- private void setCachedParam(HashMap<String,String> params, String key, int keyIndex) {
- String extra = params.get(key);
- if (extra != null) {
- mCachedParams[keyIndex+1] = extra;
+ private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
+ String valueString = params.get(key);
+ if (!TextUtils.isEmpty(valueString)) {
+ try {
+ float value = Float.parseFloat(valueString);
+ bundle.putFloat(key, value);
+ } catch (NumberFormatException ex) {
+ // don't set the value in the bundle
+ }
}
}
/**
- * Sets the OnUtteranceCompletedListener that will fire when an utterance completes.
+ * Sets the listener that will be notified when synthesis of an utterance completes.
*
- * @param listener
- * The OnUtteranceCompletedListener
+ * @param listener The listener to use.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int setOnUtteranceCompletedListener(
- final OnUtteranceCompletedListener listener) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- mITtscallback = new ITtsCallback.Stub() {
- public void utteranceCompleted(String utteranceId) throws RemoteException {
- if (listener != null) {
- listener.onUtteranceCompleted(utteranceId);
+ public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() {
+ public void utteranceCompleted(String utteranceId) {
+ if (listener != null) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
}
- }
- };
- try {
- result = mITts.registerCallback(mPackageName, mITtscallback);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - registerCallback", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - registerCallback", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - registerCallback", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ };
+ service.setCallback(getPackageName(), callback);
+ return SUCCESS;
}
- }
+ }, ERROR, "setOnUtteranceCompletedListener");
}
/**
- * Sets the speech synthesis engine to be used by its packagename.
+ * Sets the TTS engine to use.
*
- * @param enginePackageName
- * The packagename for the synthesis engine (ie, "com.svox.pico")
+ * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
+ // TODO: add @Deprecated{This method does not tell the caller when the new engine
+ // has been initialized. You should create a new TextToSpeech object with the new
+ // engine instead.}
public int setEngineByPackageName(String enginePackageName) {
- synchronized (mStartLock) {
- int result = TextToSpeech.ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- result = mITts.setEngineByPackageName(enginePackageName);
- if (result == TextToSpeech.SUCCESS){
- mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = enginePackageName;
- }
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
- }
- }
+ mRequestedEngine = enginePackageName;
+ return initTts();
}
-
/**
- * Gets the packagename of the default speech synthesis engine.
+ * Gets the package name of the default speech synthesis engine.
*
- * @return Packagename of the TTS engine that the user has chosen as their default.
+ * @return Package name of the TTS engine that the user has chosen
+ * as their default.
*/
public String getDefaultEngine() {
- synchronized (mStartLock) {
- String engineName = "";
- if (!mStarted) {
- return engineName;
+ return mEnginesHelper.getDefaultEngine();
+ }
+
+ /**
+ * Checks whether the user's settings should override settings requested by the calling
+ * application.
+ */
+ public boolean areDefaultsEnforced() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.TTS_USE_DEFAULTS, Engine.USE_DEFAULTS) == 1;
+ }
+
+ /**
+ * Gets a list of all installed TTS engines.
+ *
+ * @return A list of engine info objects. The list can be empty, but never {@code null}.
+ */
+ public List<EngineInfo> getEngines() {
+ return mEnginesHelper.getEngines();
+ }
+
+
+ private class Connection implements ServiceConnection {
+ private ITextToSpeechService mService;
+
+ 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();
+ }
+ mServiceConnection = this;
+ mService = ITextToSpeechService.Stub.asInterface(service);
+ dispatchOnInit(SUCCESS);
}
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized(mStartLock) {
+ mService = null;
+ // If this is the active connection, clear it
+ if (mServiceConnection == this) {
+ mServiceConnection = null;
+ }
+ }
+ }
+
+ public void disconnect() {
+ mContext.unbindService(this);
+ }
+
+ public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
try {
- engineName = mITts.getDefaultEngine();
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return engineName;
+ synchronized (mStartLock) {
+ if (mService == null) {
+ Log.w(TAG, method + " failed: not connected to TTS engine");
+ return errorResult;
+ }
+ return action.run(mService);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, method + " failed", ex);
+ if (reconnect) {
+ disconnect();
+ initTts();
+ }
+ return errorResult;
}
}
}
+ private interface Action<R> {
+ R run(ITextToSpeechService service) throws RemoteException;
+ }
/**
- * Returns whether or not the user is forcing their defaults to override the
- * Text-To-Speech settings set by applications.
+ * Information about an installed text-to-speech engine.
*
- * @return Whether or not defaults are enforced.
+ * @see TextToSpeech#getEngines
*/
- public boolean areDefaultsEnforced() {
- synchronized (mStartLock) {
- boolean defaultsEnforced = false;
- if (!mStarted) {
- return defaultsEnforced;
- }
- try {
- defaultsEnforced = mITts.areDefaultsEnforced();
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - areDefaultsEnforced", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - areDefaultsEnforced", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - areDefaultsEnforced", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return defaultsEnforced;
- }
+ public static class EngineInfo {
+ /**
+ * Engine package name..
+ */
+ public String name;
+ /**
+ * Localized label for the engine.
+ */
+ public String label;
+ /**
+ * Icon for the engine.
+ */
+ public int icon;
+ /**
+ * Whether this engine is a part of the system
+ * image.
+ *
+ * @hide
+ */
+ public boolean system;
+ /**
+ * The priority the engine declares for the the intent filter
+ * {@code android.intent.action.TTS_SERVICE}
+ *
+ * @hide
+ */
+ public int priority;
+
+ @Override
+ public String toString() {
+ return "EngineInfo{name=" + name + "}";
}
+
}
+
}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
new file mode 100644
index 0000000..3eea6b7
--- /dev/null
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -0,0 +1,859 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.net.Uri;
+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.MessageQueue;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech.Engine;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+
+
+/**
+ * Abstract base class for TTS engine implementations. The following methods
+ * need to be implemented.
+ *
+ * <ul>
+ * <li>{@link #onIsLanguageAvailable}</li>
+ * <li>{@link #onLoadLanguage}</li>
+ * <li>{@link #onGetLanguage}</li>
+ * <li>{@link #onSynthesizeText}</li>
+ * <li>{@link #onStop}</li>
+ * </ul>
+ *
+ * The first three deal primarily with language management, and are used to
+ * query the engine for it's support for a given language and indicate to it
+ * that requests in a given language are imminent.
+ *
+ * {@link #onSynthesizeText} is central to the engine implementation. The
+ * implementation should synthesize text as per the request parameters and
+ * return synthesized data via the supplied callback. This class and its helpers
+ * will then consume that data, which might mean queueing it for playback or writing
+ * it to a file or similar. All calls to this method will be on a single
+ * thread, which will be different from the main thread of the service. Synthesis
+ * must be synchronous which means the engine must NOT hold on the callback or call
+ * any methods on it after the method returns
+ *
+ * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if
+ * any. Any pending data from the current synthesis will be discarded.
+ */
+// TODO: Add a link to the sample TTS engine once it's done.
+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;
+ // A thread and it's associated handler for playing back any audio
+ // associated with this TTS engine. Will handle all requests except synthesis
+ // to file requests, which occur on the synthesis thread.
+ private AudioPlaybackHandler mAudioPlaybackHandler;
+
+ private CallbackMap mCallbacks;
+
+ private int mDefaultAvailability = TextToSpeech.LANG_NOT_SUPPORTED;
+
+ @Override
+ public void onCreate() {
+ if (DBG) Log.d(TAG, "onCreate()");
+ super.onCreate();
+
+ SynthThread synthThread = new SynthThread();
+ synthThread.start();
+ mSynthHandler = new SynthHandler(synthThread.getLooper());
+
+ mAudioPlaybackHandler = new AudioPlaybackHandler();
+ mAudioPlaybackHandler.start();
+
+ mCallbacks = new CallbackMap();
+
+ // Load default language
+ mDefaultAvailability = onLoadLanguage(getDefaultLanguage(),
+ getDefaultCountry(), getDefaultVariant());
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DBG) Log.d(TAG, "onDestroy()");
+
+ // Tell the synthesizer to stop
+ mSynthHandler.quit();
+ // Tell the audio playback thread to stop.
+ mAudioPlaybackHandler.quit();
+ // Unregister all callbacks.
+ mCallbacks.kill();
+
+ super.onDestroy();
+ }
+
+ /**
+ * Checks whether the engine supports a given language.
+ *
+ * Can be called on multiple threads.
+ *
+ * @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.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
+
+ /**
+ * Returns the language, country and variant currently being used by the TTS engine.
+ *
+ * Can be called on 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.
+ * @see Locale#getISO3Language()
+ * @see Locale#getISO3Country()
+ * @see Locale#getVariant()
+ */
+ protected abstract String[] onGetLanguage();
+
+ /**
+ * Notifies the engine that it should load a speech synthesis language. There is no guarantee
+ * that this method is always called before the language is used for synthesis. It is merely
+ * a hint to the engine that it will probably get some synthesis requests for this language
+ * at some point in the future.
+ *
+ * Can be called on multiple threads.
+ *
+ * @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.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ protected abstract int onLoadLanguage(String lang, String country, String variant);
+
+ /**
+ * Notifies the service that it should stop any in-progress speech synthesis.
+ * This method can be called even if no speech synthesis is currently in progress.
+ *
+ * Can be called on multiple threads, but not on the synthesis thread.
+ */
+ protected abstract void onStop();
+
+ /**
+ * Tells the service to synthesize speech from the given text. This method should
+ * block until the synthesis is finished.
+ *
+ * Called on the synthesis thread.
+ *
+ * @param request The synthesis request.
+ * @param callback The callback the the engine must use to make data available for
+ * playback or for writing to a file.
+ */
+ protected abstract void onSynthesizeText(SynthesisRequest request,
+ SynthesisCallback callback);
+
+ private boolean areDefaultsEnforced() {
+ return getSecureSettingInt(Settings.Secure.TTS_USE_DEFAULTS,
+ TextToSpeech.Engine.USE_DEFAULTS) == 1;
+ }
+
+ private int getDefaultSpeechRate() {
+ return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
+ }
+
+ private String getDefaultLanguage() {
+ return getSecureSettingString(Settings.Secure.TTS_DEFAULT_LANG,
+ Locale.getDefault().getISO3Language());
+ }
+
+ private String getDefaultCountry() {
+ return getSecureSettingString(Settings.Secure.TTS_DEFAULT_COUNTRY,
+ Locale.getDefault().getISO3Country());
+ }
+
+ private String getDefaultVariant() {
+ return getSecureSettingString(Settings.Secure.TTS_DEFAULT_VARIANT,
+ Locale.getDefault().getVariant());
+ }
+
+ private int getSecureSettingInt(String name, int defaultValue) {
+ return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
+ }
+
+ private String getSecureSettingString(String name, String defaultValue) {
+ String value = Settings.Secure.getString(getContentResolver(), name);
+ return value != null ? value : defaultValue;
+ }
+
+ /**
+ * Synthesizer thread. This thread is used to run {@link SynthHandler}.
+ */
+ private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
+
+ private boolean mFirstIdle = true;
+
+ public SynthThread() {
+ super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_AUDIO);
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ getLooper().getQueue().addIdleHandler(this);
+ }
+
+ @Override
+ public boolean queueIdle() {
+ if (mFirstIdle) {
+ mFirstIdle = false;
+ } else {
+ broadcastTtsQueueProcessingCompleted();
+ }
+ return true;
+ }
+
+ private void broadcastTtsQueueProcessingCompleted() {
+ Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
+ if (DBG) Log.d(TAG, "Broadcasting: " + i);
+ sendBroadcast(i);
+ }
+ }
+
+ private class SynthHandler extends Handler {
+
+ private SpeechItem mCurrentSpeechItem = null;
+
+ public SynthHandler(Looper looper) {
+ super(looper);
+ }
+
+ private synchronized SpeechItem getCurrentSpeechItem() {
+ return mCurrentSpeechItem;
+ }
+
+ private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
+ SpeechItem old = mCurrentSpeechItem;
+ mCurrentSpeechItem = speechItem;
+ return old;
+ }
+
+ public boolean isSpeaking() {
+ return getCurrentSpeechItem() != null;
+ }
+
+ public void quit() {
+ // Don't process any more speech items
+ getLooper().quit();
+ // Stop the current speech item
+ SpeechItem current = setCurrentSpeechItem(null);
+ if (current != null) {
+ current.stop();
+ }
+ }
+
+ /**
+ * Adds a speech item to the queue.
+ *
+ * Called on a service binder thread.
+ */
+ public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
+ if (!speechItem.isValid()) {
+ return TextToSpeech.ERROR;
+ }
+
+ if (queueMode == TextToSpeech.QUEUE_FLUSH) {
+ stop(speechItem.getCallingApp());
+ } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
+ // Stop the current speech item.
+ stop(speechItem.getCallingApp());
+ // Remove all other items from the queue.
+ removeCallbacksAndMessages(null);
+ // Remove all pending playback as well.
+ mAudioPlaybackHandler.removeAllItems();
+ }
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ setCurrentSpeechItem(speechItem);
+ speechItem.play();
+ setCurrentSpeechItem(null);
+ }
+ };
+ Message msg = Message.obtain(this, runnable);
+ // The obj is used to remove all callbacks from the given app in stop(String).
+ //
+ // Note that this string is interned, so the == comparison works.
+ msg.obj = speechItem.getCallingApp();
+ if (sendMessage(msg)) {
+ return TextToSpeech.SUCCESS;
+ } else {
+ Log.w(TAG, "SynthThread has quit");
+ return TextToSpeech.ERROR;
+ }
+ }
+
+ /**
+ * Stops all speech output and removes any utterances still in the queue for
+ * the calling app.
+ *
+ * Called on a service binder thread.
+ */
+ public int stop(String callingApp) {
+ if (TextUtils.isEmpty(callingApp)) {
+ return TextToSpeech.ERROR;
+ }
+
+ removeCallbacksAndMessages(callingApp);
+ SpeechItem current = setCurrentSpeechItem(null);
+ if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
+ current.stop();
+ }
+
+ // Remove any enqueued audio too.
+ mAudioPlaybackHandler.removePlaybackItems(callingApp);
+
+ return TextToSpeech.SUCCESS;
+ }
+ }
+
+ interface UtteranceCompletedDispatcher {
+ public void dispatchUtteranceCompleted();
+ }
+
+ /**
+ * An item in the synth thread queue.
+ */
+ private abstract class SpeechItem implements UtteranceCompletedDispatcher {
+ private final String mCallingApp;
+ protected final Bundle mParams;
+ private boolean mStarted = false;
+ private boolean mStopped = false;
+
+ public SpeechItem(String callingApp, Bundle params) {
+ mCallingApp = callingApp;
+ mParams = params;
+ }
+
+ public String getCallingApp() {
+ return mCallingApp;
+ }
+
+ /**
+ * Checker whether the item is valid. If this method returns false, the item should not
+ * be played.
+ */
+ public abstract boolean isValid();
+
+ /**
+ * Plays the speech item. Blocks until playback is finished.
+ * Must not be called more than once.
+ *
+ * Only called on the synthesis thread.
+ *
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public int play() {
+ synchronized (this) {
+ if (mStarted) {
+ throw new IllegalStateException("play() called twice");
+ }
+ mStarted = true;
+ }
+ return playImpl();
+ }
+
+ /**
+ * Stops the speech item.
+ * Must not be called more than once.
+ *
+ * Can be called on multiple threads, but not on the synthesis thread.
+ */
+ public void stop() {
+ synchronized (this) {
+ if (mStopped) {
+ throw new IllegalStateException("stop() called twice");
+ }
+ mStopped = true;
+ }
+ stopImpl();
+ }
+
+ public void dispatchUtteranceCompleted() {
+ final String utteranceId = getUtteranceId();
+ if (!TextUtils.isEmpty(utteranceId)) {
+ mCallbacks.dispatchUtteranceCompleted(getCallingApp(), utteranceId);
+ }
+ }
+
+ protected abstract int playImpl();
+
+ protected abstract void stopImpl();
+
+ public int getStreamType() {
+ return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
+ }
+
+ public float getVolume() {
+ return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
+ }
+
+ public float getPan() {
+ return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
+ }
+
+ public String getUtteranceId() {
+ return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
+ }
+
+ protected String getStringParam(String key, String defaultValue) {
+ return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
+ }
+
+ protected int getIntParam(String key, int defaultValue) {
+ return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
+ }
+
+ protected float getFloatParam(String key, float defaultValue) {
+ return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
+ }
+ }
+
+ class SynthesisSpeechItem extends SpeechItem {
+ private final String mText;
+ private final SynthesisRequest mSynthesisRequest;
+ // Non null after synthesis has started, and all accesses
+ // guarded by 'this'.
+ private AbstractSynthesisCallback mSynthesisCallback;
+
+ public SynthesisSpeechItem(String callingApp, Bundle params, String text) {
+ super(callingApp, params);
+ mText = text;
+ mSynthesisRequest = new SynthesisRequest(mText, mParams);
+ setRequestParams(mSynthesisRequest);
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ @Override
+ public boolean isValid() {
+ if (TextUtils.isEmpty(mText)) {
+ Log.w(TAG, "Got empty text");
+ return false;
+ }
+ if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){
+ Log.w(TAG, "Text too long: " + mText.length() + " chars");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ AbstractSynthesisCallback synthesisCallback;
+ synchronized (this) {
+ mSynthesisCallback = createSynthesisCallback();
+ synthesisCallback = mSynthesisCallback;
+ }
+ TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
+ return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
+ }
+
+ protected AbstractSynthesisCallback createSynthesisCallback() {
+ return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
+ mAudioPlaybackHandler, this, getCallingApp());
+ }
+
+ private void setRequestParams(SynthesisRequest request) {
+ if (areDefaultsEnforced()) {
+ request.setLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant());
+ request.setSpeechRate(getDefaultSpeechRate());
+ } else {
+ request.setLanguage(getLanguage(), getCountry(), getVariant());
+ request.setSpeechRate(getSpeechRate());
+ }
+ request.setPitch(getPitch());
+ }
+
+ @Override
+ protected void stopImpl() {
+ AbstractSynthesisCallback synthesisCallback;
+ synchronized (this) {
+ synthesisCallback = mSynthesisCallback;
+ }
+ synthesisCallback.stop();
+ TextToSpeechService.this.onStop();
+ }
+
+ public String getLanguage() {
+ return getStringParam(Engine.KEY_PARAM_LANGUAGE, getDefaultLanguage());
+ }
+
+ private boolean hasLanguage() {
+ return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
+ }
+
+ private String getCountry() {
+ if (!hasLanguage()) return getDefaultCountry();
+ return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
+ }
+
+ private String getVariant() {
+ if (!hasLanguage()) return getDefaultVariant();
+ return getStringParam(Engine.KEY_PARAM_VARIANT, "");
+ }
+
+ private int getSpeechRate() {
+ return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
+ }
+
+ private int getPitch() {
+ return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
+ }
+ }
+
+ private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
+ private final File mFile;
+
+ public SynthesisToFileSpeechItem(String callingApp, Bundle params, String text,
+ File file) {
+ super(callingApp, params, text);
+ mFile = file;
+ }
+
+ @Override
+ public boolean isValid() {
+ if (!super.isValid()) {
+ return false;
+ }
+ return checkFile(mFile);
+ }
+
+ @Override
+ protected AbstractSynthesisCallback createSynthesisCallback() {
+ return new FileSynthesisCallback(mFile);
+ }
+
+ @Override
+ protected int playImpl() {
+ int status = super.playImpl();
+ if (status == TextToSpeech.SUCCESS) {
+ dispatchUtteranceCompleted();
+ }
+ return status;
+ }
+
+ /**
+ * Checks that the given file can be used for synthesis output.
+ */
+ private boolean checkFile(File file) {
+ try {
+ if (file.exists()) {
+ Log.v(TAG, "File " + file + " exists, deleting.");
+ if (!file.delete()) {
+ Log.e(TAG, "Failed to delete " + file);
+ return false;
+ }
+ }
+ if (!file.createNewFile()) {
+ Log.e(TAG, "Can't create file " + file);
+ return false;
+ }
+ if (!file.delete()) {
+ Log.e(TAG, "Failed to delete " + file);
+ return false;
+ }
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "Can't use " + file + " due to exception " + e);
+ return false;
+ }
+ }
+ }
+
+ private class AudioSpeechItem extends SpeechItem {
+
+ private final BlockingMediaPlayer mPlayer;
+ private AudioMessageParams mToken;
+
+ public AudioSpeechItem(String callingApp, Bundle params, Uri uri) {
+ super(callingApp, params);
+ mPlayer = new BlockingMediaPlayer(TextToSpeechService.this, uri, getStreamType());
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ mToken = new AudioMessageParams(this, getCallingApp(), mPlayer);
+ mAudioPlaybackHandler.enqueueAudio(mToken);
+ return TextToSpeech.SUCCESS;
+ }
+
+ @Override
+ protected void stopImpl() {
+ if (mToken != null) {
+ mAudioPlaybackHandler.stop(mToken);
+ }
+ }
+ }
+
+ private class SilenceSpeechItem extends SpeechItem {
+ private final long mDuration;
+ private SilenceMessageParams mToken;
+
+ public SilenceSpeechItem(String callingApp, Bundle params, long duration) {
+ super(callingApp, params);
+ mDuration = duration;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ mToken = new SilenceMessageParams(this, getCallingApp(), mDuration);
+ mAudioPlaybackHandler.enqueueSilence(mToken);
+ return TextToSpeech.SUCCESS;
+ }
+
+ @Override
+ protected void stopImpl() {
+ if (mToken != null) {
+ mAudioPlaybackHandler.stop(mToken);
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
+ return mBinder;
+ }
+ return null;
+ }
+
+ /**
+ * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
+ * called called from several different threads.
+ */
+ // NOTE: All calls that are passed in a calling app are interned so that
+ // they can be used as message objects (which are tested for equality using ==).
+ private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
+
+ public int speak(String callingApp, String text, int queueMode, Bundle params) {
+ if (!checkNonNull(callingApp, text, params)) {
+ return TextToSpeech.ERROR;
+ }
+
+ SpeechItem item = new SynthesisSpeechItem(intern(callingApp), params, text);
+ return mSynthHandler.enqueueSpeechItem(queueMode, item);
+ }
+
+ public int synthesizeToFile(String callingApp, String text, String filename,
+ Bundle params) {
+ if (!checkNonNull(callingApp, text, filename, params)) {
+ return TextToSpeech.ERROR;
+ }
+
+ File file = new File(filename);
+ SpeechItem item = new SynthesisToFileSpeechItem(intern(callingApp),
+ params, text, file);
+ return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
+ }
+
+ public int playAudio(String callingApp, Uri audioUri, int queueMode, Bundle params) {
+ if (!checkNonNull(callingApp, audioUri, params)) {
+ return TextToSpeech.ERROR;
+ }
+
+ SpeechItem item = new AudioSpeechItem(intern(callingApp), params, audioUri);
+ return mSynthHandler.enqueueSpeechItem(queueMode, item);
+ }
+
+ public int playSilence(String callingApp, long duration, int queueMode, Bundle params) {
+ if (!checkNonNull(callingApp, params)) {
+ return TextToSpeech.ERROR;
+ }
+
+ SpeechItem item = new SilenceSpeechItem(intern(callingApp), params, duration);
+ return mSynthHandler.enqueueSpeechItem(queueMode, item);
+ }
+
+ public boolean isSpeaking() {
+ return mSynthHandler.isSpeaking();
+ }
+
+ public int stop(String callingApp) {
+ if (!checkNonNull(callingApp)) {
+ return TextToSpeech.ERROR;
+ }
+
+ return mSynthHandler.stop(intern(callingApp));
+ }
+
+ public String[] getLanguage() {
+ return onGetLanguage();
+ }
+
+ /*
+ * If defaults are enforced, then no language is "available" except
+ * perhaps the default language selected by the user.
+ */
+ public int isLanguageAvailable(String lang, String country, String variant) {
+ if (!checkNonNull(lang)) {
+ return TextToSpeech.ERROR;
+ }
+
+ if (areDefaultsEnforced()) {
+ if (isDefault(lang, country, variant)) {
+ return mDefaultAvailability;
+ } else {
+ return TextToSpeech.LANG_NOT_SUPPORTED;
+ }
+ }
+ return onIsLanguageAvailable(lang, country, variant);
+ }
+
+ /*
+ * There is no point loading a non default language if defaults
+ * are enforced.
+ */
+ public int loadLanguage(String lang, String country, String variant) {
+ if (!checkNonNull(lang)) {
+ return TextToSpeech.ERROR;
+ }
+
+ if (areDefaultsEnforced()) {
+ if (isDefault(lang, country, variant)) {
+ return mDefaultAvailability;
+ } else {
+ return TextToSpeech.LANG_NOT_SUPPORTED;
+ }
+ }
+ return onLoadLanguage(lang, country, variant);
+ }
+
+ public void setCallback(String packageName, ITextToSpeechCallback cb) {
+ // Note that passing in a null callback is a valid use case.
+ if (!checkNonNull(packageName)) {
+ return;
+ }
+
+ mCallbacks.setCallback(packageName, cb);
+ }
+
+ private boolean isDefault(String lang, String country, String variant) {
+ return Locale.getDefault().equals(new Locale(lang, country, variant));
+ }
+
+ private String intern(String in) {
+ // The input parameter will be non null.
+ return in.intern();
+ }
+
+ private boolean checkNonNull(Object... args) {
+ for (Object o : args) {
+ if (o == null) return false;
+ }
+ return true;
+ }
+ };
+
+ private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
+
+ private final HashMap<String, ITextToSpeechCallback> mAppToCallback
+ = new HashMap<String, ITextToSpeechCallback>();
+
+ public void setCallback(String packageName, ITextToSpeechCallback cb) {
+ synchronized (mAppToCallback) {
+ ITextToSpeechCallback old;
+ if (cb != null) {
+ register(cb, packageName);
+ old = mAppToCallback.put(packageName, cb);
+ } else {
+ old = mAppToCallback.remove(packageName);
+ }
+ if (old != null && old != cb) {
+ unregister(old);
+ }
+ }
+ }
+
+ public void dispatchUtteranceCompleted(String packageName, String utteranceId) {
+ ITextToSpeechCallback cb;
+ synchronized (mAppToCallback) {
+ cb = mAppToCallback.get(packageName);
+ }
+ if (cb == null) return;
+ try {
+ cb.utteranceCompleted(utteranceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed: " + e);
+ }
+ }
+
+ @Override
+ public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
+ String packageName = (String) cookie;
+ synchronized (mAppToCallback) {
+ mAppToCallback.remove(packageName);
+ }
+ mSynthHandler.stop(packageName);
+ }
+
+ @Override
+ public void kill() {
+ synchronized (mAppToCallback) {
+ mAppToCallback.clear();
+ super.kill();
+ }
+ }
+
+ }
+
+}
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
new file mode 100644
index 0000000..e8d74eb
--- /dev/null
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech.Engine;
+import android.speech.tts.TextToSpeech.EngineInfo;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Support class for querying the list of available engines
+ * on the device and deciding which one to use etc.
+ *
+ * Comments in this class the use the shorthand "system engines" for engines that
+ * are a part of the system image.
+ *
+ * @hide
+ */
+public class TtsEngines {
+ private final Context mContext;
+
+ public TtsEngines(Context ctx) {
+ mContext = ctx;
+ }
+
+ /**
+ * @return the default TTS engine. If the user has set a default, that
+ * value is returned else the highest ranked engine is returned,
+ * as per {@link EngineInfoComparator}.
+ */
+ public String getDefaultEngine() {
+ String engine = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.TTS_DEFAULT_SYNTH);
+ return engine != null ? engine : getHighestRankedEngineName();
+ }
+
+ /**
+ * @return the package name of the highest ranked system engine, {@code null}
+ * if no TTS engines were present in the system image.
+ */
+ public String getHighestRankedEngineName() {
+ final List<EngineInfo> engines = getEngines();
+
+ if (engines.size() > 0 && engines.get(0).system) {
+ return engines.get(0).name;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the engine info for a given engine name. Note that engines are
+ * identified by their package name.
+ */
+ public EngineInfo getEngineInfo(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ intent.setPackage(packageName);
+ List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ // Note that the current API allows only one engine per
+ // package name. Since the "engine name" is the same as
+ // the package name.
+ if (resolveInfos != null && resolveInfos.size() == 1) {
+ return getEngineInfo(resolveInfos.get(0), pm);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets a list of all installed TTS engines.
+ *
+ * @return A list of engine info objects. The list can be empty, but never {@code null}.
+ */
+ public List<EngineInfo> getEngines() {
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ List<ResolveInfo> resolveInfos =
+ pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolveInfos == null) return Collections.emptyList();
+
+ List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size());
+
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ EngineInfo engine = getEngineInfo(resolveInfo, pm);
+ if (engine != null) {
+ engines.add(engine);
+ }
+ }
+ Collections.sort(engines, EngineInfoComparator.INSTANCE);
+
+ return engines;
+ }
+
+ /**
+ * Checks whether a given engine is enabled or not. Note that all system
+ * engines are enabled by default.
+ */
+ public boolean isEngineEnabled(String engine) {
+ // System engines are enabled by default always.
+ EngineInfo info = getEngineInfo(engine);
+ if (info != null && info.system) {
+ return true;
+ }
+
+ for (String enabled : getUserEnabledEngines()) {
+ if (engine.equals(enabled)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSystemEngine(ServiceInfo info) {
+ final ApplicationInfo appInfo = info.applicationInfo;
+ return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ private EngineInfo getEngineInfo(ResolveInfo resolve, PackageManager pm) {
+ ServiceInfo service = resolve.serviceInfo;
+ if (service != null) {
+ EngineInfo engine = new EngineInfo();
+ // Using just the package name isn't great, since it disallows having
+ // multiple engines in the same package, but that's what the existing API does.
+ engine.name = service.packageName;
+ CharSequence label = service.loadLabel(pm);
+ engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString();
+ engine.icon = service.getIconResource();
+ engine.priority = resolve.priority;
+ engine.system = isSystemEngine(service);
+ return engine;
+ }
+
+ return null;
+ }
+
+ // Note that in addition to this list, all engines that are a part
+ // of the system are enabled by default.
+ private String[] getUserEnabledEngines() {
+ String str = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.TTS_ENABLED_PLUGINS);
+ if (TextUtils.isEmpty(str)) {
+ return new String[0];
+ }
+ return str.split(" ");
+ }
+
+ private static class EngineInfoComparator implements Comparator<EngineInfo> {
+ private EngineInfoComparator() { }
+
+ static EngineInfoComparator INSTANCE = new EngineInfoComparator();
+
+ /**
+ * Engines that are a part of the system image are always lesser
+ * than those that are not. Within system engines / non system engines
+ * the engines are sorted in order of their declared priority.
+ */
+ @Override
+ public int compare(EngineInfo lhs, EngineInfo rhs) {
+ if (lhs.system && !rhs.system) {
+ return -1;
+ } else if (rhs.system && !lhs.system) {
+ 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;
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9309b05..757a8c3 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -234,18 +234,17 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
* provided Metrics object (or a new one if the provided one was null)
* if boring.
*/
- public static Metrics isBoring(CharSequence text, TextPaint paint,
- Metrics metrics) {
+ public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
char[] temp = TextUtils.obtain(500);
- int len = text.length();
+ int length = text.length();
boolean boring = true;
outer:
- for (int i = 0; i < len; i += 500) {
+ for (int i = 0; i < length; i += 500) {
int j = i + 500;
- if (j > len)
- j = len;
+ if (j > length)
+ j = length;
TextUtils.getChars(text, i, j, temp, 0);
@@ -265,7 +264,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
if (boring && text instanceof Spanned) {
Spanned sp = (Spanned) text;
- Object[] styles = sp.getSpans(0, text.length(), ParagraphStyle.class);
+ Object[] styles = sp.getSpans(0, length, ParagraphStyle.class);
if (styles.length > 0) {
boring = false;
}
@@ -278,7 +277,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
}
TextLine line = TextLine.obtain();
- line.set(paint, text, 0, text.length(), Layout.DIR_LEFT_TO_RIGHT,
+ line.set(paint, text, 0, length, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
fm.width = (int) FloatMath.ceil(line.metrics(fm));
TextLine.recycle(line);
@@ -289,52 +288,63 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
}
}
- @Override public int getHeight() {
+ @Override
+ public int getHeight() {
return mBottom;
}
- @Override public int getLineCount() {
+ @Override
+ public int getLineCount() {
return 1;
}
- @Override public int getLineTop(int line) {
+ @Override
+ public int getLineTop(int line) {
if (line == 0)
return 0;
else
return mBottom;
}
- @Override public int getLineDescent(int line) {
+ @Override
+ public int getLineDescent(int line) {
return mDesc;
}
- @Override public int getLineStart(int line) {
+ @Override
+ public int getLineStart(int line) {
if (line == 0)
return 0;
else
return getText().length();
}
- @Override public int getParagraphDirection(int line) {
+ @Override
+ public int getParagraphDirection(int line) {
return DIR_LEFT_TO_RIGHT;
}
- @Override public boolean getLineContainsTab(int line) {
+ @Override
+ public boolean getLineContainsTab(int line) {
return false;
}
- @Override public float getLineMax(int line) {
+ @Override
+ public float getLineMax(int line) {
return mMax;
}
- @Override public final Directions getLineDirections(int line) {
+ @Override
+ public final Directions getLineDirections(int line) {
return Layout.DIRS_ALL_LEFT_TO_RIGHT;
}
+ @Override
public int getTopPadding() {
return mTopPadding;
}
+ @Override
public int getBottomPadding() {
return mBottomPadding;
}
diff --git a/core/java/android/text/CharSequenceIterator.java b/core/java/android/text/CharSequenceIterator.java
new file mode 100644
index 0000000..4b8ac10
--- /dev/null
+++ b/core/java/android/text/CharSequenceIterator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import java.text.CharacterIterator;
+
+/** {@hide} */
+public class CharSequenceIterator implements CharacterIterator {
+ private final CharSequence mValue;
+
+ private final int mLength;
+ private int mIndex;
+
+ public CharSequenceIterator(CharSequence value) {
+ mValue = value;
+ mLength = value.length();
+ mIndex = 0;
+ }
+
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public char current() {
+ if (mIndex == mLength) {
+ return DONE;
+ }
+ return mValue.charAt(mIndex);
+ }
+
+ /** {@inheritDoc} */
+ public int getBeginIndex() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public int getEndIndex() {
+ return mLength;
+ }
+
+ /** {@inheritDoc} */
+ public int getIndex() {
+ return mIndex;
+ }
+
+ /** {@inheritDoc} */
+ public char first() {
+ return setIndex(0);
+ }
+
+ /** {@inheritDoc} */
+ public char last() {
+ return setIndex(mLength - 1);
+ }
+
+ /** {@inheritDoc} */
+ public char next() {
+ if (mIndex == mLength) {
+ return DONE;
+ }
+ return setIndex(mIndex + 1);
+ }
+
+ /** {@inheritDoc} */
+ public char previous() {
+ if (mIndex == 0) {
+ return DONE;
+ }
+ return setIndex(mIndex - 1);
+ }
+
+ /** {@inheritDoc} */
+ public char setIndex(int index) {
+ if ((index < 0) || (index > mLength)) {
+ throw new IllegalArgumentException("Valid range is [" + 0 + "..." + mLength + "]");
+ }
+ mIndex = index;
+ return current();
+ }
+}
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index d426d12..831ccc5 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -58,6 +58,13 @@ 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/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 761c271..a81be09 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -16,14 +16,14 @@
package android.text;
-import com.android.internal.util.ArrayUtils;
-
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
/**
* @hide
*/
@@ -187,6 +187,7 @@ class MeasuredText {
w[mPos] = wid;
for (int i = mPos + 1, e = mPos + len; i < e; i++)
w[i] = 0;
+ mPos += len;
}
if (fm != null) {
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 13cb5e6..679e2cc 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -16,6 +16,8 @@
package android.text;
+import java.text.BreakIterator;
+
/**
* Utility class for manipulating cursors and selections in CharSequences.
@@ -38,7 +40,7 @@ public class Selection {
else
return -1;
}
-
+
/**
* Return the offset of the selection edge or cursor, or -1 if
* there is no selection or cursor.
@@ -57,7 +59,7 @@ public class Selection {
// private static int pin(int value, int min, int max) {
// return value < min ? 0 : (value > max ? max : value);
// }
-
+
/**
* Set the selection anchor to <code>start</code> and the selection edge
* to <code>stop</code>.
@@ -69,7 +71,7 @@ public class Selection {
int ostart = getSelectionStart(text);
int oend = getSelectionEnd(text);
-
+
if (ostart != start || oend != stop) {
text.setSpan(SELECTION_START, start, start,
Spanned.SPAN_POINT_POINT|Spanned.SPAN_INTERMEDIATE);
@@ -357,6 +359,42 @@ public class Selection {
return true;
}
+ /** {@hide} */
+ public static interface PositionIterator {
+ public static final int DONE = BreakIterator.DONE;
+
+ public int preceding(int position);
+ public int following(int position);
+ }
+
+ /** {@hide} */
+ public static boolean moveToPreceding(
+ Spannable text, PositionIterator iter, boolean extendSelection) {
+ final int offset = iter.preceding(getSelectionEnd(text));
+ if (offset != PositionIterator.DONE) {
+ if (extendSelection) {
+ extendSelection(text, offset);
+ } else {
+ setSelection(text, offset);
+ }
+ }
+ return true;
+ }
+
+ /** {@hide} */
+ public static boolean moveToFollowing(
+ Spannable text, PositionIterator iter, boolean extendSelection) {
+ final int offset = iter.following(getSelectionEnd(text));
+ if (offset != PositionIterator.DONE) {
+ if (extendSelection) {
+ extendSelection(text, offset);
+ } else {
+ setSelection(text, offset);
+ }
+ }
+ return true;
+ }
+
private static int findEdge(Spannable text, Layout layout, int dir) {
int pt = getSelectionEnd(text);
int line = layout.getLineForOffset(pt);
@@ -419,7 +457,7 @@ public class Selection {
private static final class START implements NoCopySpan { }
private static final class END implements NoCopySpan { }
-
+
/*
* Public constants
*/
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index ea5cdfe..6bde802 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -20,6 +20,7 @@ import com.android.internal.util.ArrayUtils;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.text.style.SuggestionSpan;
import java.lang.reflect.Array;
@@ -277,8 +278,7 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
TextWatcher[] recipients = null;
if (notify)
- recipients = sendTextWillChange(start, end - start,
- tbend - tbstart);
+ recipients = sendTextWillChange(start, end - start, tbend - tbstart);
for (int i = mSpanCount - 1; i >= 0; i--) {
if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
@@ -353,6 +353,7 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
// no need for span fixup on pure insertion
if (tbend > tbstart && end - start == 0) {
if (notify) {
+ removeSuggestionSpans(start, end);
sendTextChange(recipients, start, end - start, tbend - tbstart);
sendTextHasChanged(recipients);
}
@@ -384,20 +385,10 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
}
// remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE
- // XXX send notification on removal
-
if (mSpanEnds[i] < mSpanStarts[i]) {
- System.arraycopy(mSpans, i + 1,
- mSpans, i, mSpanCount - (i + 1));
- System.arraycopy(mSpanStarts, i + 1,
- mSpanStarts, i, mSpanCount - (i + 1));
- System.arraycopy(mSpanEnds, i + 1,
- mSpanEnds, i, mSpanCount - (i + 1));
- System.arraycopy(mSpanFlags, i + 1,
- mSpanFlags, i, mSpanCount - (i + 1));
-
- mSpanCount--;
+ removeSpan(i);
}
+ removeSuggestionSpans(start, end);
}
if (notify) {
@@ -408,6 +399,32 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
return ret;
}
+ /**
+ * Removes the SuggestionSpan that overlap the [start, end] range, and that would
+ * not make sense anymore after the change.
+ */
+ private void removeSuggestionSpans(int start, int end) {
+ for (int i = mSpanCount - 1; i >= 0; i--) {
+ final int spanEnd = mSpanEnds[i];
+ final int spanSpart = mSpanStarts[i];
+ if ((mSpans[i] instanceof SuggestionSpan) && (
+ (spanSpart < start && spanEnd > start) ||
+ (spanSpart < end && spanEnd > end))) {
+ removeSpan(i);
+ }
+ }
+ }
+
+ private void removeSpan(int i) {
+ // XXX send notification on removal
+ System.arraycopy(mSpans, i + 1, mSpans, i, mSpanCount - (i + 1));
+ System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, mSpanCount - (i + 1));
+ System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, mSpanCount - (i + 1));
+ System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, mSpanCount - (i + 1));
+
+ mSpanCount--;
+ }
+
// Documentation from interface
public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
return replace(start, end, tb, 0, tb.length());
@@ -465,16 +482,15 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
mGapStart++;
mGapLength--;
- if (mGapLength < 1)
+ if (mGapLength < 1) {
new Exception("mGapLength < 1").printStackTrace();
+ }
int oldlen = (end + 1) - start;
- int inserted = change(false, start + 1, start + 1,
- tb, tbstart, tbend);
+ int inserted = change(false, start + 1, start + 1, tb, tbstart, tbend);
change(false, start, start + 1, "", 0, 0);
- change(false, start + inserted, start + inserted + oldlen - 1,
- "", 0, 0);
+ change(false, start + inserted, start + inserted + oldlen - 1, "", 0, 0);
/*
* Special case to keep the cursor in the same position
@@ -1170,6 +1186,35 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
}
/**
+ * 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.
@@ -1245,7 +1290,6 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
private int[] mSpanFlags;
private int mSpanCount;
- private static final int MARK = 1;
private static final int POINT = 2;
private static final int PARAGRAPH = 3;
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index a826a97..9e48eff 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -235,6 +235,8 @@ public class StaticLayout extends Layout {
} else {
MetricAffectingSpan[] spans =
spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, spanned,
+ MetricAffectingSpan.class);
measured.addStyleRun(paint, spans, spanLen, fm);
}
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 155870a..f5249611 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -43,6 +43,8 @@ import android.util.Log;
* @hide
*/
class TextLine {
+ private static final boolean DEBUG = false;
+
private TextPaint mPaint;
private CharSequence mText;
private int mStart;
@@ -56,7 +58,7 @@ class TextLine {
private Spanned mSpanned;
private final TextPaint mWorkPaint = new TextPaint();
- private static TextLine[] cached = new TextLine[3];
+ private static final TextLine[] sCached = new TextLine[3];
/**
* Returns a new TextLine from the shared pool.
@@ -65,16 +67,19 @@ class TextLine {
*/
static TextLine obtain() {
TextLine tl;
- synchronized (cached) {
- for (int i = cached.length; --i >= 0;) {
- if (cached[i] != null) {
- tl = cached[i];
- cached[i] = null;
+ synchronized (sCached) {
+ for (int i = sCached.length; --i >= 0;) {
+ if (sCached[i] != null) {
+ tl = sCached[i];
+ sCached[i] = null;
return tl;
}
}
}
tl = new TextLine();
+ if (DEBUG) {
+ Log.v("TLINE", "new: " + tl);
+ }
return tl;
}
@@ -89,10 +94,10 @@ class TextLine {
tl.mText = null;
tl.mPaint = null;
tl.mDirections = null;
- synchronized(cached) {
- for (int i = 0; i < cached.length; ++i) {
- if (cached[i] == null) {
- cached[i] = tl;
+ synchronized(sCached) {
+ for (int i = 0; i < sCached.length; ++i) {
+ if (sCached[i] == null) {
+ sCached[i] = tl;
break;
}
}
@@ -126,12 +131,12 @@ class TextLine {
boolean hasReplacement = false;
if (text instanceof Spanned) {
mSpanned = (Spanned) text;
- hasReplacement = mSpanned.getSpans(start, limit,
- ReplacementSpan.class).length > 0;
+ ReplacementSpan[] spans = mSpanned.getSpans(start, limit, ReplacementSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
+ hasReplacement = spans.length > 0;
}
- mCharsValid = hasReplacement || hasTabs ||
- directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
if (mCharsValid) {
if (mChars == null || mChars.length < mLen) {
@@ -146,10 +151,11 @@ class TextLine {
// zero-width characters.
char[] chars = mChars;
for (int i = start, inext; i < limit; i = inext) {
- inext = mSpanned.nextSpanTransition(i, limit,
- ReplacementSpan.class);
- if (mSpanned.getSpans(i, inext, ReplacementSpan.class)
- .length > 0) { // transition into a span
+ inext = mSpanned.nextSpanTransition(i, limit, ReplacementSpan.class);
+ ReplacementSpan[] spans = mSpanned.getSpans(i, inext, ReplacementSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
+ if (spans.length > 0) {
+ // transition into a span
chars[i - start] = '\ufffc';
for (int j = i - start + 1, e = inext - start; j < e; ++j) {
chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
@@ -173,11 +179,11 @@ class TextLine {
void draw(Canvas c, float x, int top, int y, int bottom) {
if (!mHasTabs) {
if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
- drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
+ drawRun(c, 0, mLen, false, x, top, y, bottom, false);
return;
}
if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
- drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
+ drawRun(c, 0, mLen, true, x, top, y, bottom, false);
return;
}
}
@@ -196,7 +202,6 @@ class TextLine {
boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
int segstart = runStart;
- char[] chars = mChars;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
int codept = 0;
Bitmap bm = null;
@@ -215,7 +220,7 @@ class TextLine {
}
if (j == runLimit || codept == '\t' || bm != null) {
- h += drawRun(c, i, segstart, j, runIsRtl, x+h, top, y, bottom,
+ h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
i != lastRunIndex || j != mLen);
if (codept == '\t') {
@@ -274,10 +279,10 @@ class TextLine {
if (!mHasTabs) {
if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
- return measureRun(0, 0, offset, mLen, false, fmi);
+ return measureRun(0, offset, mLen, false, fmi);
}
if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
- return measureRun(0, 0, offset, mLen, true, fmi);
+ return measureRun(0, offset, mLen, true, fmi);
}
}
@@ -314,14 +319,14 @@ class TextLine {
boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
if (inSegment && advance) {
- return h += measureRun(i, segstart, offset, j, runIsRtl, fmi);
+ return h += measureRun(segstart, offset, j, runIsRtl, fmi);
}
- float w = measureRun(i, segstart, j, j, runIsRtl, fmi);
+ float w = measureRun(segstart, j, j, runIsRtl, fmi);
h += advance ? w : -w;
if (inSegment) {
- return h += measureRun(i, segstart, offset, j, runIsRtl, null);
+ return h += measureRun(segstart, offset, j, runIsRtl, null);
}
if (codept == '\t') {
@@ -352,8 +357,8 @@ class TextLine {
/**
* Draws a unidirectional (but possibly multi-styled) run of text.
*
+ *
* @param c the canvas to draw on
- * @param runIndex the index of this directional run
* @param start the line-relative start
* @param limit the line-relative limit
* @param runIsRtl true if the run is right-to-left
@@ -365,25 +370,25 @@ class TextLine {
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
- private float drawRun(Canvas c, int runIndex, int start,
+ private float drawRun(Canvas c, int start,
int limit, boolean runIsRtl, float x, int top, int y, int bottom,
boolean needWidth) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null);
- handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top,
+ float w = -measureRun(start, limit, limit, runIsRtl, null);
+ handleRun(start, limit, limit, runIsRtl, c, x + w, top,
y, bottom, null, false);
return w;
}
- return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
+ return handleRun(start, limit, limit, runIsRtl, c, x, top,
y, bottom, null, needWidth);
}
/**
* Measures a unidirectional (but possibly multi-styled) run of text.
*
- * @param runIndex the run index
+ *
* @param start the line-relative start of the run
* @param offset the offset to measure to, between start and limit inclusive
* @param limit the line-relative limit of the run
@@ -393,10 +398,9 @@ class TextLine {
* @return the signed width from the start of the run to the leading edge
* of the character at offset, based on the run (not paragraph) direction
*/
- private float measureRun(int runIndex, int start,
- int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) {
- return handleRun(runIndex, start, offset, limit, runIsRtl, null,
- 0, 0, 0, 0, fmi, true);
+ private float measureRun(int start, int offset, int limit, boolean runIsRtl,
+ FontMetricsInt fmi) {
+ return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
}
/**
@@ -628,6 +632,7 @@ class TextLine {
MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
mStart + spanLimit, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
if (spans.length > 0) {
ReplacementSpan replacement = null;
@@ -749,9 +754,9 @@ class TextLine {
/**
* Utility function for measuring and rendering a replacement.
*
+ *
* @param replacement the replacement
* @param wp the work paint
- * @param runIndex the run index
* @param start the start of the run
* @param limit the limit of the run
* @param runIsRtl true if the run is right-to-left
@@ -766,7 +771,7 @@ class TextLine {
* valid if needWidth is true
*/
private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
- int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
+ int start, int limit, boolean runIsRtl, Canvas c,
float x, int top, int y, int bottom, FontMetricsInt fmi,
boolean needWidth) {
@@ -794,7 +799,7 @@ class TextLine {
* Utility function for handling a unidirectional run. The run must not
* contain tabs or emoji but can contain styles.
*
- * @param runIndex the run index
+ *
* @param start the line-relative start of the run
* @param measureLimit the offset to measure to, between start and limit inclusive
* @param limit the limit of the run
@@ -809,10 +814,17 @@ class TextLine {
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
- private float handleRun(int runIndex, int start, int measureLimit,
+ private float handleRun(int start, int measureLimit,
int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
int bottom, FontMetricsInt fmi, boolean needWidth) {
+ // Case of an empty line, make sure we update fmi according to mPaint
+ if (start == measureLimit) {
+ TextPaint wp = mWorkPaint;
+ wp.set(mPaint);
+ return handleText(wp, 0, 0, 0, 0, runIsRtl, c, x, top, y, bottom, fmi, needWidth);
+ }
+
// Shaping needs to take into account context up to metric boundaries,
// but rendering needs to take into account character style boundaries.
// So we iterate through metric runs to get metric bounds,
@@ -834,6 +846,7 @@ class TextLine {
mlimit = inext < measureLimit ? inext : measureLimit;
MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
mStart + mlimit, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
if (spans.length > 0) {
ReplacementSpan replacement = null;
@@ -849,7 +862,7 @@ class TextLine {
}
if (replacement != null) {
- x += handleReplacement(replacement, wp, runIndex, i,
+ x += handleReplacement(replacement, wp, i,
mlimit, runIsRtl, c, x, top, y, bottom, fmi,
needWidth || mlimit < measureLimit);
continue;
@@ -867,6 +880,7 @@ class TextLine {
CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
mStart + jnext, CharacterStyle.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class);
wp.set(mPaint);
for (int k = 0; k < spans.length; k++) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index cdb7228..6741059 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -37,6 +37,7 @@ import android.text.style.ScaleXSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
+import android.text.style.SuggestionSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
@@ -44,6 +45,7 @@ import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Printer;
+import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.regex.Pattern;
@@ -54,7 +56,7 @@ public class TextUtils {
public static void getChars(CharSequence s, int start, int end,
char[] dest, int destoff) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (c == String.class)
((String) s).getChars(start, end, dest, destoff);
@@ -75,7 +77,7 @@ public class TextUtils {
}
public static int indexOf(CharSequence s, char ch, int start) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (c == String.class)
return ((String) s).indexOf(ch, start);
@@ -84,7 +86,7 @@ public class TextUtils {
}
public static int indexOf(CharSequence s, char ch, int start, int end) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (s instanceof GetChars || c == StringBuffer.class ||
c == StringBuilder.class || c == String.class) {
@@ -125,7 +127,7 @@ public class TextUtils {
}
public static int lastIndexOf(CharSequence s, char ch, int last) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (c == String.class)
return ((String) s).lastIndexOf(ch, last);
@@ -142,7 +144,7 @@ public class TextUtils {
int end = last + 1;
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (s instanceof GetChars || c == StringBuffer.class ||
c == StringBuilder.class || c == String.class) {
@@ -499,6 +501,7 @@ public class TextUtils {
return new String(buf);
}
+ @Override
public String toString() {
return subSequence(0, length()).toString();
}
@@ -563,6 +566,8 @@ public class TextUtils {
public static final int TEXT_APPEARANCE_SPAN = 17;
/** @hide */
public static final int ANNOTATION = 18;
+ /** @hide */
+ public static final int SUGGESTION_SPAN = 19;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -621,7 +626,7 @@ public class TextUtils {
* Read and return a new CharSequence, possibly with styles,
* from the parcel.
*/
- public CharSequence createFromParcel(Parcel p) {
+ public CharSequence createFromParcel(Parcel p) {
int kind = p.readInt();
String string = p.readString();
@@ -714,6 +719,10 @@ public class TextUtils {
readSpan(p, sp, new Annotation(p));
break;
+ case SUGGESTION_SPAN:
+ readSpan(p, sp, new SuggestionSpan(p));
+ break;
+
default:
throw new RuntimeException("bogus span encoding " + kind);
}
@@ -766,7 +775,7 @@ public class TextUtils {
if (where >= 0)
tb.setSpan(sources[i], where, where + sources[i].length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
for (int i = 0; i < sources.length; i++) {
@@ -1120,7 +1129,6 @@ public class TextUtils {
int remaining = commaCount + 1;
int ok = 0;
- int okRemaining = remaining;
String okFormat = "";
int w = 0;
@@ -1152,7 +1160,6 @@ public class TextUtils {
if (w + moreWid <= avail) {
ok = i + 1;
- okRemaining = remaining;
okFormat = format;
}
}
@@ -1185,6 +1192,7 @@ public class TextUtils {
MetricAffectingSpan.class);
MetricAffectingSpan[] spans = sp.getSpans(
spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, sp, MetricAffectingSpan.class);
width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
}
}
@@ -1543,6 +1551,56 @@ public class TextUtils {
return false;
}
+ /**
+ * Removes empty spans from the <code>spans</code> array.
+ *
+ * When parsing a Spanned using {@link Spanned#nextSpanTransition(int, int, Class)}, empty spans
+ * will (correctly) create span transitions, and calling getSpans on a slice of text bounded by
+ * one of these transitions will (correctly) include the empty overlapping span.
+ *
+ * However, these empty spans should not be taken into account when layouting or rendering the
+ * string and this method provides a way to filter getSpans' results accordingly.
+ *
+ * @param spans A list of spans retrieved using {@link Spanned#getSpans(int, int, Class)} from
+ * the <code>spanned</code>
+ * @param spanned The Spanned from which spans were extracted
+ * @return A subset of spans where empty spans ({@link Spanned#getSpanStart(Object)} ==
+ * {@link Spanned#getSpanEnd(Object)} have been removed. The initial order is preserved
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T[] removeEmptySpans(T[] spans, Spanned spanned, Class<T> klass) {
+ T[] copy = null;
+ int count = 0;
+
+ for (int i = 0; i < spans.length; i++) {
+ final T span = spans[i];
+ final int start = spanned.getSpanStart(span);
+ final int end = spanned.getSpanEnd(span);
+
+ if (start == end) {
+ if (copy == null) {
+ copy = (T[]) Array.newInstance(klass, spans.length - 1);
+ System.arraycopy(spans, 0, copy, 0, i);
+ count = i;
+ }
+ } else {
+ if (copy != null) {
+ copy[count] = span;
+ count++;
+ }
+ }
+ }
+
+ if (copy != null) {
+ T[] result = (T[]) Array.newInstance(klass, count);
+ System.arraycopy(copy, 0, result, 0, count);
+ return result;
+ } else {
+ return spans;
+ }
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
}
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index a61ff13..fe96565 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -193,6 +193,20 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
}
}
+ /** {@hide} */
+ @Override
+ protected boolean leftWord(TextView widget, Spannable buffer) {
+ mWordIterator.setCharSequence(buffer);
+ return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
+ }
+
+ /** {@hide} */
+ @Override
+ protected boolean rightWord(TextView widget, Spannable buffer) {
+ mWordIterator.setCharSequence(buffer);
+ return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer));
+ }
+
@Override
protected boolean home(TextView widget, Spannable buffer) {
return lineStart(widget, buffer);
@@ -205,7 +219,8 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
- int initialScrollX = -1, initialScrollY = -1;
+ int initialScrollX = -1;
+ int initialScrollY = -1;
final int action = event.getAction();
if (action == MotionEvent.ACTION_UP) {
@@ -219,8 +234,8 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
if (action == MotionEvent.ACTION_DOWN) {
boolean cap = isSelecting(buffer);
if (cap) {
- int offset = widget.getOffset((int) event.getX(), (int) event.getY());
-
+ int offset = widget.getOffsetForPosition(event.getX(), event.getY());
+
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
// Disallow intercepting of the touch events, so that
@@ -244,7 +259,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
// Update selection as we're moving the selection area.
// Get the current touch position
- int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+ int offset = widget.getOffsetForPosition(event.getX(), event.getY());
Selection.extendSelection(buffer, offset);
return true;
@@ -260,7 +275,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
return true;
}
- int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+ int offset = widget.getOffsetForPosition(event.getX(), event.getY());
if (isSelecting(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
@@ -308,6 +323,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
return sInstance;
}
+ private WordIterator mWordIterator = new WordIterator();
private static final Object LAST_TAP_DOWN = new Object();
private static ArrowKeyMovementMethod sInstance;
diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java
index 94c6ed0..f554b90 100644
--- a/core/java/android/text/method/BaseMovementMethod.java
+++ b/core/java/android/text/method/BaseMovementMethod.java
@@ -164,6 +164,9 @@ public class BaseMovementMethod implements MovementMethod {
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return left(widget, buffer);
} else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return leftWord(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
KeyEvent.META_ALT_ON)) {
return lineStart(widget, buffer);
}
@@ -173,6 +176,9 @@ public class BaseMovementMethod implements MovementMethod {
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return right(widget, buffer);
} else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return rightWord(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
KeyEvent.META_ALT_ON)) {
return lineEnd(widget, buffer);
}
@@ -217,12 +223,18 @@ public class BaseMovementMethod implements MovementMethod {
case KeyEvent.KEYCODE_MOVE_HOME:
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return home(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return top(widget, buffer);
}
break;
case KeyEvent.KEYCODE_MOVE_END:
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return end(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return bottom(widget, buffer);
}
break;
}
@@ -349,6 +361,16 @@ public class BaseMovementMethod implements MovementMethod {
return false;
}
+ /** {@hide} */
+ protected boolean leftWord(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /** {@hide} */
+ protected boolean rightWord(TextView widget, Spannable buffer) {
+ return false;
+ }
+
/**
* Performs a home movement action.
* Moves the cursor or scrolls to the start of the line or to the top of the
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
new file mode 100644
index 0000000..af524ee
--- /dev/null
+++ b/core/java/android/text/method/WordIterator.java
@@ -0,0 +1,221 @@
+
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.method;
+
+import android.text.CharSequenceIterator;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spanned;
+import android.text.TextWatcher;
+
+import java.text.BreakIterator;
+import java.text.CharacterIterator;
+import java.util.Locale;
+
+/**
+ * Walks through cursor positions at word boundaries. Internally uses
+ * {@link BreakIterator#getWordInstance()}, and caches {@link CharSequence}
+ * for performance reasons.
+ *
+ * Also provides methods to determine word boundaries.
+ * {@hide}
+ */
+public class WordIterator implements Selection.PositionIterator {
+ private CharSequence mCurrent;
+ private boolean mCurrentDirty = false;
+
+ private BreakIterator mIterator;
+
+ /**
+ * Constructs a WordIterator using the default locale.
+ */
+ public WordIterator() {
+ this(Locale.getDefault());
+ }
+
+ /**
+ * Constructs a new WordIterator for the specified locale.
+ * @param locale The locale to be used when analysing the text.
+ */
+ public WordIterator(Locale locale) {
+ mIterator = BreakIterator.getWordInstance(locale);
+ }
+
+ private final TextWatcher mWatcher = new TextWatcher() {
+ /** {@inheritDoc} */
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mCurrentDirty = true;
+ }
+
+ /** {@inheritDoc} */
+ public void afterTextChanged(Editable s) {
+ // ignored
+ }
+ };
+
+ public void setCharSequence(CharSequence incoming) {
+ // When incoming is different object, move listeners to new sequence
+ // and mark as dirty so we reload contents.
+ if (mCurrent != incoming) {
+ if (mCurrent instanceof Editable) {
+ ((Editable) mCurrent).removeSpan(mWatcher);
+ }
+
+ if (incoming instanceof Editable) {
+ ((Editable) incoming).setSpan(
+ mWatcher, 0, incoming.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+
+ mCurrent = incoming;
+ mCurrentDirty = true;
+ }
+
+ if (mCurrentDirty) {
+ final CharacterIterator charIterator = new CharSequenceIterator(mCurrent);
+ mIterator.setText(charIterator);
+
+ mCurrentDirty = false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int preceding(int offset) {
+ do {
+ offset = mIterator.preceding(offset);
+ if (offset == BreakIterator.DONE || isOnLetterOrDigit(offset)) {
+ break;
+ }
+ } while (true);
+
+ return offset;
+ }
+
+ /** {@inheritDoc} */
+ public int following(int offset) {
+ do {
+ offset = mIterator.following(offset);
+ if (offset == BreakIterator.DONE || isAfterLetterOrDigit(offset)) {
+ break;
+ }
+ } while (true);
+
+ return offset;
+ }
+
+ /** If <code>offset</code> is within a word, returns the index of the first character of that
+ * word, otherwise returns BreakIterator.DONE.
+ *
+ * The offsets that are considered to be part of a word are the indexes of its characters,
+ * <i>as well as</i> the index of its last character plus one.
+ * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
+ *
+ * Valid range for offset is [0..textLength] (note the inclusive upper bound).
+ * The returned value is within [0..offset] or BreakIterator.DONE.
+ *
+ * @throws IllegalArgumentException is offset is not valid.
+ */
+ public int getBeginning(int offset) {
+ checkOffsetIsValid(offset);
+
+ if (isOnLetterOrDigit(offset)) {
+ if (mIterator.isBoundary(offset)) {
+ return offset;
+ } else {
+ return mIterator.preceding(offset);
+ }
+ } else {
+ if (isAfterLetterOrDigit(offset)) {
+ return mIterator.preceding(offset);
+ }
+ }
+ return BreakIterator.DONE;
+ }
+
+ /** If <code>offset</code> is within a word, returns the index of the last character of that
+ * word plus one, otherwise returns BreakIterator.DONE.
+ *
+ * The offsets that are considered to be part of a word are the indexes of its characters,
+ * <i>as well as</i> the index of its last character plus one.
+ * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
+ *
+ * Valid range for offset is [0..textLength] (note the inclusive upper bound).
+ * The returned value is within [offset..textLength] or BreakIterator.DONE.
+ *
+ * @throws IllegalArgumentException is offset is not valid.
+ */
+ public int getEnd(int offset) {
+ checkOffsetIsValid(offset);
+
+ if (isAfterLetterOrDigit(offset)) {
+ if (mIterator.isBoundary(offset)) {
+ return offset;
+ } else {
+ return mIterator.following(offset);
+ }
+ } else {
+ if (isOnLetterOrDigit(offset)) {
+ return mIterator.following(offset);
+ }
+ }
+ return BreakIterator.DONE;
+ }
+
+ private boolean isAfterLetterOrDigit(int offset) {
+ if (offset - 1 >= 0) {
+ final char previousChar = mCurrent.charAt(offset - 1);
+ if (Character.isLetterOrDigit(previousChar)) return true;
+ if (offset - 2 >= 0) {
+ final char previousPreviousChar = mCurrent.charAt(offset - 2);
+ if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
+ final int codePoint = Character.toCodePoint(previousPreviousChar, previousChar);
+ return Character.isLetterOrDigit(codePoint);
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isOnLetterOrDigit(int offset) {
+ final int length = mCurrent.length();
+ if (offset < length) {
+ final char currentChar = mCurrent.charAt(offset);
+ if (Character.isLetterOrDigit(currentChar)) return true;
+ if (offset + 1 < length) {
+ final char nextChar = mCurrent.charAt(offset + 1);
+ if (Character.isSurrogatePair(currentChar, nextChar)) {
+ final int codePoint = Character.toCodePoint(currentChar, nextChar);
+ return Character.isLetterOrDigit(codePoint);
+ }
+ }
+ }
+ return false;
+ }
+
+ private void checkOffsetIsValid(int offset) {
+ if (offset < 0 || offset > mCurrent.length()) {
+ final String message = "Invalid offset: " + offset +
+ ". Valid range is [0, " + mCurrent.length() + "]";
+ throw new IllegalArgumentException(message);
+ }
+ }
+}
diff --git a/core/java/android/text/style/SuggestionSpan.aidl b/core/java/android/text/style/SuggestionSpan.aidl
new file mode 100644
index 0000000..3c56cfe
--- /dev/null
+++ b/core/java/android/text/style/SuggestionSpan.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.style;
+
+parcelable SuggestionSpan;
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
new file mode 100644
index 0000000..240ad9b
--- /dev/null
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.style;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Holds suggestion candidates of words under this span.
+ */
+public class SuggestionSpan implements ParcelableSpan {
+ /**
+ * Flag for indicating that the input is verbatim. TextView refers to this flag to determine
+ * how it displays a word with SuggestionSpan.
+ */
+ public static final int FLAG_VERBATIM = 0x0001;
+
+ public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
+ public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
+ public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
+ public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
+
+ public static final int SUGGESTIONS_MAX_SIZE = 5;
+
+ /*
+ * TODO: Needs to check the validity and add a feature that TextView will change
+ * the current IME to the other IME which is specified in SuggestionSpan.
+ * An IME needs to set the span by specifying the target IME and Subtype of SuggestionSpan.
+ * And the current IME might want to specify any IME as the target IME including other IMEs.
+ */
+
+ private final int mFlags;
+ private final String[] mSuggestions;
+ private final String mLocaleString;
+ private final String mNotificationTargetClassName;
+ private final int mHashCode;
+
+ /*
+ * TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo
+ * and InputMethodSubtype.
+ */
+
+ /**
+ * @param context Context for the application
+ * @param suggestions Suggestions for the string under the span
+ * @param flags Additional flags indicating how this span is handled in TextView
+ */
+ public SuggestionSpan(Context context, String[] suggestions, int flags) {
+ this(context, null, suggestions, flags, null);
+ }
+
+ /**
+ * @param locale Locale of the suggestions
+ * @param suggestions Suggestions for the string under the span
+ * @param flags Additional flags indicating how this span is handled in TextView
+ */
+ public SuggestionSpan(Locale locale, String[] suggestions, int flags) {
+ this(null, locale, suggestions, flags, null);
+ }
+
+ /**
+ * @param context Context for the application
+ * @param locale locale Locale of the suggestions
+ * @param suggestions Suggestions for the string under the span
+ * @param flags Additional flags indicating how this span is handled in TextView
+ * @param notificationTargetClass if not null, this class will get notified when the user
+ * selects one of the suggestions.
+ */
+ public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags,
+ Class<?> notificationTargetClass) {
+ final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
+ mSuggestions = Arrays.copyOf(suggestions, N);
+ mFlags = flags;
+ if (context != null && locale == null) {
+ mLocaleString = context.getResources().getConfiguration().locale.toString();
+ } else {
+ mLocaleString = locale.toString();
+ }
+ if (notificationTargetClass != null) {
+ mNotificationTargetClassName = notificationTargetClass.getCanonicalName();
+ } else {
+ mNotificationTargetClassName = "";
+ }
+ mHashCode = hashCodeInternal(
+ mFlags, mSuggestions, mLocaleString, mNotificationTargetClassName);
+ }
+
+ public SuggestionSpan(Parcel src) {
+ mSuggestions = src.readStringArray();
+ mFlags = src.readInt();
+ mLocaleString = src.readString();
+ mNotificationTargetClassName = src.readString();
+ mHashCode = src.readInt();
+ }
+
+ /**
+ * @return suggestions
+ */
+ public String[] getSuggestions() {
+ return mSuggestions;
+ }
+
+ /**
+ * @return locale of suggestions
+ */
+ public String getLocale() {
+ return mLocaleString;
+ }
+
+ /**
+ * @return The name of the class to notify. The class of the original IME package will receive
+ * a notification when the user selects one of the suggestions. The notification will include
+ * the original string, the suggested replacement string as well as the hashCode of this span.
+ * The class will get notified by an intent that has those information.
+ * This is an internal API because only the framework should know the class name.
+ *
+ * @hide
+ */
+ public String getNotificationTargetClassName() {
+ return mNotificationTargetClassName;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringArray(mSuggestions);
+ dest.writeInt(mFlags);
+ dest.writeString(mLocaleString);
+ dest.writeString(mNotificationTargetClassName);
+ dest.writeInt(mHashCode);
+ }
+
+ @Override
+ public int getSpanTypeId() {
+ return TextUtils.SUGGESTION_SPAN;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof SuggestionSpan) {
+ return ((SuggestionSpan)o).hashCode() == mHashCode;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ private static int hashCodeInternal(int flags, String[] suggestions,String locale,
+ String notificationTargetClassName) {
+ return Arrays.hashCode(new Object[] {SystemClock.uptimeMillis(), flags, suggestions, locale,
+ notificationTargetClassName});
+ }
+
+ public static final Parcelable.Creator<SuggestionSpan> CREATOR =
+ new Parcelable.Creator<SuggestionSpan>() {
+ @Override
+ public SuggestionSpan createFromParcel(Parcel source) {
+ return new SuggestionSpan(source);
+ }
+
+ @Override
+ public SuggestionSpan[] newArray(int size) {
+ return new SuggestionSpan[size];
+ }
+ };
+}
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index de929e3..deed713 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -51,10 +51,9 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl
* to determine the color. The <code>appearance</code> should be,
* for example, <code>android.R.style.TextAppearance_Small</code>,
* and the <code>colorList</code> should be, for example,
- * <code>android.R.styleable.Theme_textColorDim</code>.
+ * <code>android.R.styleable.Theme_textColorPrimary</code>.
*/
- public TextAppearanceSpan(Context context, int appearance,
- int colorList) {
+ public TextAppearanceSpan(Context context, int appearance, int colorList) {
ColorStateList textColor;
TypedArray a =
diff --git a/core/java/android/util/CalendarUtils.java b/core/java/android/util/CalendarUtils.java
deleted file mode 100644
index b2b4897..0000000
--- a/core/java/android/util/CalendarUtils.java
+++ /dev/null
@@ -1,348 +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.util;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.provider.Calendar.CalendarCache;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-
-import java.util.Formatter;
-import java.util.HashSet;
-import java.util.Locale;
-
-/**
- * A class containing utility methods related to Calendar apps.
- *
- * @hide
- */
-public class CalendarUtils {
- private static final boolean DEBUG = false;
- private static final String TAG = "CalendarUtils";
-
- /**
- * This class contains methods specific to reading and writing time zone
- * values.
- */
- public static class TimeZoneUtils {
- private static final String[] TIMEZONE_TYPE_ARGS = { CalendarCache.TIMEZONE_KEY_TYPE };
- private static final String[] TIMEZONE_INSTANCES_ARGS =
- { CalendarCache.TIMEZONE_KEY_INSTANCES };
-
- private static StringBuilder mSB = new StringBuilder(50);
- private static Formatter mF = new Formatter(mSB, Locale.getDefault());
- private volatile static boolean mFirstTZRequest = true;
- private volatile static boolean mTZQueryInProgress = false;
-
- private volatile static boolean mUseHomeTZ = false;
- private volatile static String mHomeTZ = Time.getCurrentTimezone();
-
- private static HashSet<Runnable> mTZCallbacks = new HashSet<Runnable>();
- private static int mToken = 1;
- private static AsyncTZHandler mHandler;
-
- // The name of the shared preferences file. This name must be maintained for historical
- // reasons, as it's what PreferenceManager assigned the first time the file was created.
- private final String mPrefsName;
-
- /**
- * This is the key used for writing whether or not a home time zone should
- * be used in the Calendar app to the Calendar Preferences.
- */
- public static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
- /**
- * This is the key used for writing the time zone that should be used if
- * home time zones are enabled for the Calendar app.
- */
- public static final String KEY_HOME_TZ = "preferences_home_tz";
-
- /**
- * This is a helper class for handling the async queries and updates for the
- * time zone settings in Calendar.
- */
- private class AsyncTZHandler extends AsyncQueryHandler {
- public AsyncTZHandler(ContentResolver cr) {
- super(cr);
- }
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- synchronized (mTZCallbacks) {
- if (cursor == null) {
- mTZQueryInProgress = false;
- mFirstTZRequest = true;
- return;
- }
-
- boolean writePrefs = false;
- // Check the values in the db
- int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
- int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
- while(cursor.moveToNext()) {
- String key = cursor.getString(keyColumn);
- String value = cursor.getString(valueColumn);
- if (TextUtils.equals(key, CalendarCache.TIMEZONE_KEY_TYPE)) {
- boolean useHomeTZ = !TextUtils.equals(
- value, CalendarCache.TIMEZONE_TYPE_AUTO);
- if (useHomeTZ != mUseHomeTZ) {
- writePrefs = true;
- mUseHomeTZ = useHomeTZ;
- }
- } else if (TextUtils.equals(
- key, CalendarCache.TIMEZONE_KEY_INSTANCES_PREVIOUS)) {
- if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
- writePrefs = true;
- mHomeTZ = value;
- }
- }
- }
- cursor.close();
- if (writePrefs) {
- SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
- // Write the prefs
- setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
- setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
- }
-
- mTZQueryInProgress = false;
- for (Runnable callback : mTZCallbacks) {
- if (callback != null) {
- callback.run();
- }
- }
- mTZCallbacks.clear();
- }
- }
- }
-
- /**
- * The name of the file where the shared prefs for Calendar are stored
- * must be provided. All activities within an app should provide the
- * same preferences name or behavior may become erratic.
- *
- * @param prefsName
- */
- public TimeZoneUtils(String prefsName) {
- mPrefsName = prefsName;
- }
-
- /**
- * Formats a date or a time range according to the local conventions.
- *
- * This formats a date/time range using Calendar's time zone and the
- * local conventions for the region of the device.
- *
- * If the {@link DateUtils#FORMAT_UTC} flag is used it will pass in
- * the UTC time zone instead.
- *
- * @param context the context is required only if the time is shown
- * @param startMillis the start time in UTC milliseconds
- * @param endMillis the end time in UTC milliseconds
- * @param flags a bit mask of options See
- * {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
- * @return a string containing the formatted date/time range.
- */
- public String formatDateRange(Context context, long startMillis,
- long endMillis, int flags) {
- String date;
- String tz;
- if ((flags & DateUtils.FORMAT_UTC) != 0) {
- tz = Time.TIMEZONE_UTC;
- } else {
- tz = getTimeZone(context, null);
- }
- synchronized (mSB) {
- mSB.setLength(0);
- date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
- tz).toString();
- }
- return date;
- }
-
- /**
- * Writes a new home time zone to the db.
- *
- * Updates the home time zone in the db asynchronously and updates
- * the local cache. Sending a time zone of
- * {@link CalendarCache#TIMEZONE_TYPE_AUTO} will cause it to be set
- * to the device's time zone. null or empty tz will be ignored.
- *
- * @param context The calling activity
- * @param timeZone The time zone to set Calendar to, or
- * {@link CalendarCache#TIMEZONE_TYPE_AUTO}
- */
- public void setTimeZone(Context context, String timeZone) {
- if (TextUtils.isEmpty(timeZone)) {
- if (DEBUG) {
- Log.d(TAG, "Empty time zone, nothing to be done.");
- }
- return;
- }
- boolean updatePrefs = false;
- synchronized (mTZCallbacks) {
- if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
- if (mUseHomeTZ) {
- updatePrefs = true;
- }
- mUseHomeTZ = false;
- } else {
- if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
- updatePrefs = true;
- }
- mUseHomeTZ = true;
- mHomeTZ = timeZone;
- }
- }
- if (updatePrefs) {
- // Write the prefs
- SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
- setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
- setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
-
- // Update the db
- ContentValues values = new ContentValues();
- if (mHandler != null) {
- mHandler.cancelOperation(mToken);
- }
-
- mHandler = new AsyncTZHandler(context.getContentResolver());
-
- // skip 0 so query can use it
- if (++mToken == 0) {
- mToken = 1;
- }
-
- // Write the use home tz setting
- values.put(CalendarCache.VALUE, mUseHomeTZ ? CalendarCache.TIMEZONE_TYPE_HOME
- : CalendarCache.TIMEZONE_TYPE_AUTO);
- mHandler.startUpdate(mToken, null, CalendarCache.URI, values, CalendarCache.WHERE,
- TIMEZONE_TYPE_ARGS);
-
- // If using a home tz write it to the db
- if (mUseHomeTZ) {
- ContentValues values2 = new ContentValues();
- values2.put(CalendarCache.VALUE, mHomeTZ);
- mHandler.startUpdate(mToken, null, CalendarCache.URI, values2,
- CalendarCache.WHERE, TIMEZONE_INSTANCES_ARGS);
- }
- }
- }
-
- /**
- * Gets the time zone that Calendar should be displayed in
- *
- * This is a helper method to get the appropriate time zone for Calendar. If this
- * is the first time this method has been called it will initiate an asynchronous
- * query to verify that the data in preferences is correct. The callback supplied
- * will only be called if this query returns a value other than what is stored in
- * preferences and should cause the calling activity to refresh anything that
- * depends on calling this method.
- *
- * @param context The calling activity
- * @param callback The runnable that should execute if a query returns new values
- * @return The string value representing the time zone Calendar should display
- */
- public String getTimeZone(Context context, Runnable callback) {
- synchronized (mTZCallbacks){
- if (mFirstTZRequest) {
- mTZQueryInProgress = true;
- mFirstTZRequest = false;
-
- SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
- mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
- mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
-
- // When the async query returns it should synchronize on
- // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
- // preferences, set mTZQueryInProgress to false, and call all
- // the runnables in mTZCallbacks.
- if (mHandler == null) {
- mHandler = new AsyncTZHandler(context.getContentResolver());
- }
- mHandler.startQuery(0, context, CalendarCache.URI, CalendarCache.POJECTION,
- null, null, null);
- }
- if (mTZQueryInProgress) {
- mTZCallbacks.add(callback);
- }
- }
- return mUseHomeTZ ? mHomeTZ : Time.getCurrentTimezone();
- }
-
- /**
- * Forces a query of the database to check for changes to the time zone.
- * This should be called if another app may have modified the db. If a
- * query is already in progress the callback will be added to the list
- * of callbacks to be called when it returns.
- *
- * @param context The calling activity
- * @param callback The runnable that should execute if a query returns
- * new values
- */
- public void forceDBRequery(Context context, Runnable callback) {
- synchronized (mTZCallbacks){
- if (mTZQueryInProgress) {
- mTZCallbacks.add(callback);
- return;
- }
- mFirstTZRequest = true;
- getTimeZone(context, callback);
- }
- }
- }
-
- /**
- * A helper method for writing a String value to the preferences
- * asynchronously.
- *
- * @param context A context with access to the correct preferences
- * @param key The preference to write to
- * @param value The value to write
- */
- public static void setSharedPreference(SharedPreferences prefs, String key, String value) {
-// SharedPreferences prefs = getSharedPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString(key, value);
- editor.apply();
- }
-
- /**
- * A helper method for writing a boolean value to the preferences
- * asynchronously.
- *
- * @param context A context with access to the correct preferences
- * @param key The preference to write to
- * @param value The value to write
- */
- public static void setSharedPreference(SharedPreferences prefs, String key, boolean value) {
-// SharedPreferences prefs = getSharedPreferences(context, prefsName);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(key, value);
- editor.apply();
- }
-
- /** Return a properly configured SharedPreferences instance */
- public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
- return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
- }
-}
diff --git a/core/java/android/util/Config.java b/core/java/android/util/Config.java
deleted file mode 100644
index becb882..0000000
--- a/core/java/android/util/Config.java
+++ /dev/null
@@ -1,60 +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.util;
-
-/**
- * Build configuration. The constants in this class vary depending
- * on release vs. debug build.
- * {@more}
- */
-public final class Config {
- /** @hide */ public Config() {}
-
- /**
- * If this is a debug build, this field will be true.
- */
- public static final boolean DEBUG = ConfigBuildFlags.DEBUG;
-
- /*
- * Deprecated fields
- * TODO: Remove platform references to these and @hide them.
- */
-
- /**
- * @deprecated Use {@link #DEBUG} instead.
- */
- @Deprecated
- public static final boolean RELEASE = !DEBUG;
-
- /**
- * @deprecated Always false.
- */
- @Deprecated
- public static final boolean PROFILE = false;
-
- /**
- * @deprecated Always false.
- */
- @Deprecated
- public static final boolean LOGV = false;
-
- /**
- * @deprecated Always true.
- */
- @Deprecated
- public static final boolean LOGD = true;
-}
diff --git a/core/java/android/util/FinitePool.java b/core/java/android/util/FinitePool.java
index 3ef8293..4ae21ad 100644
--- a/core/java/android/util/FinitePool.java
+++ b/core/java/android/util/FinitePool.java
@@ -69,6 +69,7 @@ class FinitePool<T extends Poolable<T>> implements Pool<T> {
if (element != null) {
element.setNextPoolable(null);
+ element.setPooled(false);
mManager.onAcquired(element);
}
@@ -76,9 +77,13 @@ class FinitePool<T extends Poolable<T>> implements Pool<T> {
}
public void release(T element) {
+ if (element.isPooled()) {
+ throw new IllegalArgumentException("Element already in the pool.");
+ }
if (mInfinite || mPoolCount < mLimit) {
mPoolCount++;
element.setNextPoolable(mRoot);
+ element.setPooled(true);
mRoot = element;
}
mManager.onReleased(element);
diff --git a/core/java/android/util/FloatProperty.java b/core/java/android/util/FloatProperty.java
new file mode 100644
index 0000000..a67b3cb
--- /dev/null
+++ b/core/java/android/util/FloatProperty.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.util.Property;
+
+/**
+ * An implementation of {@link android.util.Property} to be used specifically with fields of type
+ * <code>float</code>. This type-specific subclass enables performance benefit by allowing
+ * calls to a {@link #set(Object, Float) set()} function that takes the primitive
+ * <code>float</code> type and avoids autoboxing and other overhead associated with the
+ * <code>Float</code> class.
+ *
+ * @param <T> The class on which the Property is declared.
+ *
+ * @hide
+ */
+public abstract class FloatProperty<T> extends Property<T, Float> {
+
+ public FloatProperty(String name) {
+ super(Float.class, name);
+ }
+
+ /**
+ * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing
+ * with fields of type <code>float</code>.
+ */
+ public abstract void setValue(T object, float value);
+
+ @Override
+ final public void set(T object, Float value) {
+ setValue(object, value);
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/util/IntProperty.java b/core/java/android/util/IntProperty.java
new file mode 100644
index 0000000..459d6b2
--- /dev/null
+++ b/core/java/android/util/IntProperty.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.util.Property;
+
+/**
+ * An implementation of {@link android.util.Property} to be used specifically with fields of type
+ * <code>int</code>. This type-specific subclass enables performance benefit by allowing
+ * calls to a {@link #set(Object, Integer) set()} function that takes the primitive
+ * <code>int</code> type and avoids autoboxing and other overhead associated with the
+ * <code>Integer</code> class.
+ *
+ * @param <T> The class on which the Property is declared.
+ *
+ * @hide
+ */
+public abstract class IntProperty<T> extends Property<T, Integer> {
+
+ public IntProperty(String name) {
+ super(Integer.class, name);
+ }
+
+ /**
+ * A type-specific override of the {@link #set(Object, Integer)} that is faster when dealing
+ * with fields of type <code>int</code>.
+ */
+ public abstract void setValue(T object, int value);
+
+ @Override
+ final public void set(T object, Integer value) {
+ set(object, value.intValue());
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
index 8f44895..132b595 100644
--- a/core/java/android/util/JsonReader.java
+++ b/core/java/android/util/JsonReader.java
@@ -16,12 +16,13 @@
package android.util;
+import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
-import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
+import libcore.internal.StringPool;
/**
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
@@ -86,7 +87,11 @@ import java.util.List;
*
* public List<Message> readJsonStream(InputStream in) throws IOException {
* JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
- * return readMessagesArray(reader);
+ * try {
+ * return readMessagesArray(reader);
+ * } finally {
+ * reader.close();
+ * }
* }
*
* public List<Message> readMessagesArray(JsonReader reader) throws IOException {
@@ -173,6 +178,8 @@ public final class JsonReader implements Closeable {
private static final String TRUE = "true";
private static final String FALSE = "false";
+ private final StringPool stringPool = new StringPool();
+
/** The input JSON. */
private final Reader in;
@@ -832,7 +839,7 @@ public final class JsonReader implements Closeable {
if (skipping) {
return "skipped!";
} else if (builder == null) {
- return new String(buffer, start, pos - start - 1);
+ return stringPool.get(buffer, start, pos - start - 1);
} else {
builder.append(buffer, start, pos - start - 1);
return builder.toString();
@@ -930,7 +937,7 @@ public final class JsonReader implements Closeable {
} else if (skipping) {
result = "skipped!";
} else if (builder == null) {
- result = new String(buffer, pos, i);
+ result = stringPool.get(buffer, pos, i);
} else {
builder.append(buffer, pos, i);
result = builder.toString();
@@ -964,7 +971,7 @@ public final class JsonReader implements Closeable {
if (pos + 4 > limit && !fillBuffer(4)) {
throw syntaxError("Unterminated escape sequence");
}
- String hex = new String(buffer, pos, 4);
+ String hex = stringPool.get(buffer, pos, 4);
pos += 4;
return (char) Integer.parseInt(hex, 16);
@@ -1036,7 +1043,7 @@ public final class JsonReader implements Closeable {
value = FALSE;
return JsonToken.BOOLEAN;
} else {
- value = new String(buffer, valuePos, valueLength);
+ value = stringPool.get(buffer, valuePos, valueLength);
return decodeNumber(buffer, valuePos, valueLength);
}
}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 38903ab..1c3709f 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -20,6 +20,7 @@ import com.android.internal.os.RuntimeInit;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.net.UnknownHostException;
/**
* API for sending log output.
@@ -302,6 +303,17 @@ public final class Log {
if (tr == null) {
return "";
}
+
+ // This is to reduce the amount of log spew that apps do in the non-error
+ // condition of the network being unavailable.
+ Throwable t = tr;
+ while (t != null) {
+ if (t instanceof UnknownHostException) {
+ return "";
+ }
+ t = t.getCause();
+ }
+
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index 834dac3..5540000 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -304,7 +304,8 @@ public class LruCache<K, V> {
}
/**
- * Returns the number of times {@link #get} returned a value.
+ * Returns the number of times {@link #get} returned a value that was
+ * already present in the cache.
*/
public synchronized final int hitCount() {
return hitCount;
diff --git a/core/java/android/util/NoSuchPropertyException.java b/core/java/android/util/NoSuchPropertyException.java
new file mode 100644
index 0000000..b93f983
--- /dev/null
+++ b/core/java/android/util/NoSuchPropertyException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+/**
+ * Thrown when code requests a {@link Property} on a class that does
+ * not expose the appropriate method or field.
+ *
+ * @see Property#of(java.lang.Class, java.lang.Class, java.lang.String)
+ */
+public class NoSuchPropertyException extends RuntimeException {
+
+ public NoSuchPropertyException(String s) {
+ super(s);
+ }
+
+}
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
new file mode 100644
index 0000000..5b19ecd
--- /dev/null
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.net.SntpClient;
+import android.os.SystemClock;
+
+/**
+ * {@link TrustedTime} that connects with a remote NTP server as its remote
+ * trusted time source.
+ *
+ * @hide
+ */
+public class NtpTrustedTime implements TrustedTime {
+ private String mNtpServer;
+ private long mNtpTimeout;
+
+ private boolean mHasCache;
+ private long mCachedNtpTime;
+ private long mCachedNtpElapsedRealtime;
+ private long mCachedNtpCertainty;
+
+ public NtpTrustedTime() {
+ }
+
+ public void setNtpServer(String server, long timeout) {
+ mNtpServer = server;
+ mNtpTimeout = timeout;
+ }
+
+ /** {@inheritDoc} */
+ public boolean forceRefresh() {
+ if (mNtpServer == null) {
+ // missing server, so no trusted time available
+ return false;
+ }
+
+ final SntpClient client = new SntpClient();
+ if (client.requestTime(mNtpServer, (int) mNtpTimeout)) {
+ mHasCache = true;
+ mCachedNtpTime = client.getNtpTime();
+ mCachedNtpElapsedRealtime = client.getNtpTimeReference();
+ mCachedNtpCertainty = client.getRoundTripTime() / 2;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean hasCache() {
+ return mHasCache;
+ }
+
+ /** {@inheritDoc} */
+ public long getCacheAge() {
+ if (mHasCache) {
+ return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
+ } else {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public long getCacheCertainty() {
+ if (mHasCache) {
+ return mCachedNtpCertainty;
+ } else {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public long currentTimeMillis() {
+ if (!mHasCache) {
+ throw new IllegalStateException("Missing authoritative time source");
+ }
+
+ // current time is age after the last ntp cache; callers who
+ // want fresh values will hit makeAuthoritative() first.
+ return mCachedNtpTime + getCacheAge();
+ }
+}
diff --git a/core/java/android/util/Poolable.java b/core/java/android/util/Poolable.java
index fd9bd9b..87e0529 100644
--- a/core/java/android/util/Poolable.java
+++ b/core/java/android/util/Poolable.java
@@ -22,4 +22,6 @@ package android.util;
public interface Poolable<T> {
void setNextPoolable(T element);
T getNextPoolable();
+ boolean isPooled();
+ void setPooled(boolean isPooled);
}
diff --git a/core/java/android/util/Property.java b/core/java/android/util/Property.java
new file mode 100644
index 0000000..146db80
--- /dev/null
+++ b/core/java/android/util/Property.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+
+/**
+ * A property is an abstraction that can be used to represent a <emb>mutable</em> value that is held
+ * in a <em>host</em> object. The Property's {@link #set(Object, Object)} or {@link #get(Object)}
+ * methods can be implemented in terms of the private fields of the host object, or via "setter" and
+ * "getter" methods or by some other mechanism, as appropriate.
+ *
+ * @param <T> The class on which the property is declared.
+ * @param <V> The type that this property represents.
+ */
+public abstract class Property<T, V> {
+
+ private final String mName;
+ private final Class<V> mType;
+
+ /**
+ * This factory method creates and returns a Property given the <code>class</code> and
+ * <code>name</code> parameters, where the <code>"name"</code> parameter represents either:
+ * <ul>
+ * <li>a public <code>getName()</code> method on the class which takes no arguments, plus an
+ * optional public <code>setName()</code> method which takes a value of the same type
+ * returned by <code>getName()</code>
+ * <li>a public <code>isName()</code> method on the class which takes no arguments, plus an
+ * optional public <code>setName()</code> method which takes a value of the same type
+ * returned by <code>isName()</code>
+ * <li>a public <code>name</code> field on the class
+ * </ul>
+ *
+ * <p>If either of the get/is method alternatives is found on the class, but an appropriate
+ * <code>setName()</code> method is not found, the <code>Property</code> will be
+ * {@link #isReadOnly() readOnly}. Calling the {@link #set(Object, Object)} method on such
+ * a property is allowed, but will have no effect.</p>
+ *
+ * <p>If neither the methods nor the field are found on the class a
+ * {@link NoSuchPropertyException} exception will be thrown.</p>
+ */
+ public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) {
+ return new ReflectiveProperty<T, V>(hostType, valueType, name);
+ }
+
+ /**
+ * A constructor that takes an identifying name and {@link #getType() type} for the property.
+ */
+ public Property(Class<V> type, String name) {
+ mName = name;
+ mType = type;
+ }
+
+ /**
+ * Returns true if the {@link #set(Object, Object)} method does not set the value on the target
+ * object (in which case the {@link #set(Object, Object) set()} method should throw a {@link
+ * NoSuchPropertyException} exception). This may happen if the Property wraps functionality that
+ * allows querying the underlying value but not setting it. For example, the {@link #of(Class,
+ * Class, String)} factory method may return a Property with name "foo" for an object that has
+ * only a <code>getFoo()</code> or <code>isFoo()</code> method, but no matching
+ * <code>setFoo()</code> method.
+ */
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ /**
+ * Sets the value on <code>object</code> which this property represents. If the method is unable
+ * to set the value on the target object it will throw an {@link UnsupportedOperationException}
+ * exception.
+ */
+ public void set(T object, V value) {
+ throw new UnsupportedOperationException("Property " + getName() +" is read-only");
+ }
+
+ /**
+ * Returns the current value that this property represents on the given <code>object</code>.
+ */
+ public abstract V get(T object);
+
+ /**
+ * Returns the name for this property.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the type for this property.
+ */
+ public Class<V> getType() {
+ return mType;
+ }
+}
diff --git a/core/java/android/util/ReflectiveProperty.java b/core/java/android/util/ReflectiveProperty.java
new file mode 100644
index 0000000..6832240
--- /dev/null
+++ b/core/java/android/util/ReflectiveProperty.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Internal class to automatically generate a Property for a given class/name pair, given the
+ * specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)}
+ */
+class ReflectiveProperty<T, V> extends Property<T, V> {
+
+ private static final String PREFIX_GET = "get";
+ private static final String PREFIX_IS = "is";
+ private static final String PREFIX_SET = "set";
+ private Method mSetter;
+ private Method mGetter;
+ private Field mField;
+
+ /**
+ * For given property name 'name', look for getName/isName method or 'name' field.
+ * Also look for setName method (optional - could be readonly). Failing method getters and
+ * field results in throwing NoSuchPropertyException.
+ *
+ * @param propertyHolder The class on which the methods or field are found
+ * @param name The name of the property, where this name is capitalized and appended to
+ * "get" and "is to search for the appropriate methods. If the get/is methods are not found,
+ * the constructor will search for a field with that exact name.
+ */
+ public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) {
+ // TODO: cache reflection info for each new class/name pair
+ super(valueType, name);
+ char firstLetter = Character.toUpperCase(name.charAt(0));
+ String theRest = name.substring(1);
+ String capitalizedName = firstLetter + theRest;
+ String getterName = PREFIX_GET + capitalizedName;
+ try {
+ mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null);
+ } catch (NoSuchMethodException e) {
+ // getName() not available - try isName() instead
+ getterName = PREFIX_IS + capitalizedName;
+ try {
+ mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null);
+ } catch (NoSuchMethodException e1) {
+ // Try public field instead
+ try {
+ mField = propertyHolder.getField(name);
+ Class fieldType = mField.getType();
+ if (!typesMatch(valueType, fieldType)) {
+ throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " +
+ "does not match Property type (" + valueType + ")");
+ }
+ return;
+ } catch (NoSuchFieldException e2) {
+ // no way to access property - throw appropriate exception
+ throw new NoSuchPropertyException("No accessor method or field found for"
+ + " property with name " + name);
+ }
+ }
+ }
+ Class getterType = mGetter.getReturnType();
+ // Check to make sure our getter type matches our valueType
+ if (!typesMatch(valueType, getterType)) {
+ throw new NoSuchPropertyException("Underlying type (" + getterType + ") " +
+ "does not match Property type (" + valueType + ")");
+ }
+ String setterName = PREFIX_SET + capitalizedName;
+ try {
+ mSetter = propertyHolder.getMethod(setterName, getterType);
+ } catch (NoSuchMethodException ignored) {
+ // Okay to not have a setter - just a readonly property
+ }
+ }
+
+ /**
+ * Utility method to check whether the type of the underlying field/method on the target
+ * object matches the type of the Property. The extra checks for primitive types are because
+ * generics will force the Property type to be a class, whereas the type of the underlying
+ * method/field will probably be a primitive type instead. Accept float as matching Float,
+ * etc.
+ */
+ private boolean typesMatch(Class<V> valueType, Class getterType) {
+ if (getterType != valueType) {
+ if (getterType.isPrimitive()) {
+ return (getterType == float.class && valueType == Float.class) ||
+ (getterType == int.class && valueType == Integer.class) ||
+ (getterType == boolean.class && valueType == Boolean.class) ||
+ (getterType == long.class && valueType == Long.class) ||
+ (getterType == double.class && valueType == Double.class) ||
+ (getterType == short.class && valueType == Short.class) ||
+ (getterType == byte.class && valueType == Byte.class) ||
+ (getterType == char.class && valueType == Character.class);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void set(T object, V value) {
+ if (mSetter != null) {
+ try {
+ mSetter.invoke(object, value);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ } else if (mField != null) {
+ try {
+ mField.set(object, value);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ }
+ } else {
+ throw new UnsupportedOperationException("Property " + getName() +" is read-only");
+ }
+ }
+
+ @Override
+ public V get(T object) {
+ if (mGetter != null) {
+ try {
+ return (V) mGetter.invoke(object, (Object[])null);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ } else if (mField != null) {
+ try {
+ return (V) mField.get(object);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ }
+ }
+ // Should not get here: there should always be a non-null getter or field
+ throw new AssertionError();
+ }
+
+ /**
+ * Returns false if there is no setter or public field underlying this Property.
+ */
+ @Override
+ public boolean isReadOnly() {
+ return (mSetter == null && mField == null);
+ }
+}
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 9042505..93299eb 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -19,7 +19,7 @@ package android.util;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import org.apache.harmony.luni.internal.util.ZoneInfoDB;
+import libcore.util.ZoneInfoDB;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java
new file mode 100644
index 0000000..263d782
--- /dev/null
+++ b/core/java/android/util/TrustedTime.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * Interface that provides trusted time information, possibly coming from an NTP
+ * server. Implementations may cache answers until {@link #forceRefresh()}.
+ *
+ * @hide
+ */
+public interface TrustedTime {
+ /**
+ * Force update with an external trusted time source, returning {@code true}
+ * when successful.
+ */
+ public boolean forceRefresh();
+
+ /**
+ * Check if this instance has cached a response from a trusted time source.
+ */
+ public boolean hasCache();
+
+ /**
+ * Return time since last trusted time source contact, or
+ * {@link Long#MAX_VALUE} if never contacted.
+ */
+ public long getCacheAge();
+
+ /**
+ * Return certainty of cached trusted time in milliseconds, or
+ * {@link Long#MAX_VALUE} if never contacted. Smaller values are more
+ * precise.
+ */
+ public long getCacheCertainty();
+
+ /**
+ * Return current time similar to {@link System#currentTimeMillis()},
+ * possibly using a cached authoritative time source.
+ */
+ public long currentTimeMillis();
+}
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index b0c33e5..041e8a8 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -16,23 +16,21 @@
package android.util;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import org.apache.harmony.xml.ExpatReader;
+import org.kxml2.io.KXmlParser;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-
-import org.apache.harmony.xml.ExpatPullParser;
-import org.apache.harmony.xml.ExpatReader;
+import org.xmlpull.v1.XmlSerializer;
/**
* XML utility methods.
@@ -46,7 +44,7 @@ public class Xml {
* @see <a href="http://xmlpull.org/v1/doc/features.html#relaxed">
* specification</a>
*/
- public static String FEATURE_RELAXED = ExpatPullParser.FEATURE_RELAXED;
+ public static String FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed";
/**
* Parses the given xml string and fires events on the given SAX handler.
@@ -57,8 +55,7 @@ public class Xml {
XMLReader reader = new ExpatReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(new StringReader(xml)));
- }
- catch (IOException e) {
+ } catch (IOException e) {
throw new AssertionError(e);
}
}
@@ -88,16 +85,17 @@ public class Xml {
}
/**
- * Creates a new pull parser with namespace support.
- *
- * <p><b>Note:</b> This is actually slower than the SAX parser, and it's not
- * fully implemented. If you need a fast, mostly implemented pull parser,
- * use this. If you need a complete implementation, use KXML.
+ * Returns a new pull parser with namespace support.
*/
public static XmlPullParser newPullParser() {
- ExpatPullParser parser = new ExpatPullParser();
- parser.setNamespaceProcessingEnabled(true);
- return parser;
+ try {
+ KXmlParser parser = new KXmlParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (XmlPullParserException e) {
+ throw new AssertionError();
+ }
}
/**
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 14f2e9d..d5cad96 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -39,6 +39,12 @@ import android.text.TextUtils;
* An implementation of Canvas on top of OpenGL ES 2.0.
*/
class GLES20Canvas extends HardwareCanvas {
+ // Must match modifiers used in the JNI layer
+ private static final int MODIFIER_NONE = 0;
+ private static final int MODIFIER_SHADOW = 1;
+ private static final int MODIFIER_SHADER = 2;
+ private static final int MODIFIER_COLOR_FILTER = 4;
+
private final boolean mOpaque;
private int mRenderer;
@@ -154,8 +160,10 @@ class GLES20Canvas extends HardwareCanvas {
// Hardware layers
///////////////////////////////////////////////////////////////////////////
+ static native int nCreateTextureLayer(int[] layerInfo);
static native int nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo);
static native void nResizeLayer(int layerId, int width, int height, int[] layerInfo);
+ static native void nUpdateTextureLayer(int layerId, int width, int height, int surface);
static native void nDestroyLayer(int layerId);
static native void nDestroyLayerDeferred(int layerId);
@@ -193,6 +201,14 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nSetViewport(int renderer, int width, int height);
/**
+ * Preserves the back buffer of the current surface after a buffer swap.
+ * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current
+ * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL
+ * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
+ *
+ * @return True if the swap behavior was successfully changed,
+ * false otherwise.
+ *
* @hide
*/
public static boolean preserveBackBuffer() {
@@ -200,6 +216,21 @@ class GLES20Canvas extends HardwareCanvas {
}
private static native boolean nPreserveBackBuffer();
+
+ /**
+ * Indicates whether the current surface preserves its back buffer
+ * after a buffer swap.
+ *
+ * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED,
+ * false otherwise
+ *
+ * @hide
+ */
+ public static boolean isBackBufferPreserved() {
+ return nIsBackBufferPreserved();
+ }
+
+ private static native boolean nIsBackBufferPreserved();
@Override
void onPreDraw(Rect dirty) {
@@ -253,20 +284,27 @@ class GLES20Canvas extends HardwareCanvas {
private static native boolean nDrawDisplayList(int renderer, int displayList,
int width, int height, Rect dirty);
+ @Override
+ void outputDisplayList(DisplayList displayList) {
+ nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).mNativeDisplayList);
+ }
+
+ private static native void nOutputDisplayList(int renderer, int displayList);
+
///////////////////////////////////////////////////////////////////////////
// Hardware layer
///////////////////////////////////////////////////////////////////////////
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
final GLES20Layer glLayer = (GLES20Layer) layer;
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
}
private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint);
-
+
void interrupt() {
nInterrupt(mRenderer);
}
@@ -455,10 +493,10 @@ class GLES20Canvas extends HardwareCanvas {
public int saveLayer(float left, float top, float right, float bottom, Paint paint,
int saveFlags) {
if (left < right && top < bottom) {
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
int count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
return count;
}
return save(saveFlags);
@@ -527,10 +565,10 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle,
useCenter, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawArc(int renderer, float left, float top,
@@ -545,11 +583,11 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
// Shaders are ignored when drawing patches
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
}
private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks,
@@ -558,10 +596,10 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmap(
@@ -570,11 +608,11 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
matrix.native_instance, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmap(int renderer, int bitmap, byte[] buff,
@@ -583,7 +621,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
int left, top, right, bottom;
@@ -600,17 +638,17 @@ class GLES20Canvas extends HardwareCanvas {
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
@Override
public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, src.left, src.top, src.right,
src.bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer,
@@ -621,13 +659,13 @@ class GLES20Canvas extends HardwareCanvas {
public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
int width, int height, boolean hasAlpha, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config);
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, b.mNativeBitmap, b.mBuffer, x, y, nativePaint);
b.recycle();
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
}
@Override
@@ -655,11 +693,11 @@ class GLES20Canvas extends HardwareCanvas {
colors = null;
colorOffset = 0;
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
verts, vertOffset, colors, colorOffset, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmapMesh(int renderer, int bitmap, byte[] buffer,
@@ -668,9 +706,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawCircle(float cx, float cy, float radius, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawCircle(int renderer, float cx, float cy,
@@ -702,9 +740,9 @@ class GLES20Canvas extends HardwareCanvas {
if ((offset | count) < 0 || offset + count > pts.length) {
throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawLines(int renderer, float[] points,
@@ -717,9 +755,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawOval(RectF oval, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawOval(int renderer, float left, float top,
@@ -734,7 +772,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPath(Path path, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
if (path.isSimplePath) {
if (path.rects != null) {
nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
@@ -742,7 +780,7 @@ class GLES20Canvas extends HardwareCanvas {
} else {
nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
}
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawPath(int renderer, int path, int paint);
@@ -767,19 +805,24 @@ class GLES20Canvas extends HardwareCanvas {
public void drawPoint(float x, float y, Paint paint) {
mPoint[0] = x;
mPoint[1] = y;
- drawPoints(mPoint, 0, 1, paint);
+ drawPoints(mPoint, 0, 2, paint);
}
@Override
- public void drawPoints(float[] pts, int offset, int count, Paint paint) {
- // TODO: Implement
+ public void drawPoints(float[] pts, Paint paint) {
+ drawPoints(pts, 0, pts.length, paint);
}
@Override
- public void drawPoints(float[] pts, Paint paint) {
- drawPoints(pts, 0, pts.length / 2, paint);
+ public void drawPoints(float[] pts, int offset, int count, Paint paint) {
+ int modifiers = setupModifiers(paint);
+ nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
+ private static native void nDrawPoints(int renderer, float[] points,
+ int offset, int count, int paint);
+
@Override
public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
// TODO: Implement
@@ -792,9 +835,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawRect(float left, float top, float right, float bottom, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawRect(int renderer, float left, float top,
@@ -817,10 +860,10 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
rx, ry, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawRoundRect(int renderer, float left, float top,
@@ -832,11 +875,11 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -845,7 +888,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
@@ -862,7 +905,7 @@ class GLES20Canvas extends HardwareCanvas {
TemporaryBuffer.recycle(buf);
}
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -872,11 +915,11 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -885,12 +928,12 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawText(String text, float x, float y, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -915,12 +958,12 @@ class GLES20Canvas extends HardwareCanvas {
throw new IllegalArgumentException("Unknown direction: " + dir);
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -934,7 +977,7 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
int flags = dir == 0 ? 0 : 1;
if (text instanceof String || text instanceof SpannedString ||
@@ -954,7 +997,7 @@ class GLES20Canvas extends HardwareCanvas {
TemporaryBuffer.recycle(buf);
}
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -968,43 +1011,57 @@ class GLES20Canvas extends HardwareCanvas {
// TODO: Implement
}
- private boolean setupModifiers(Paint paint) {
- boolean hasModifier = false;
+ private int setupModifiers(Bitmap b, Paint paint) {
+ if (b.getConfig() == Bitmap.Config.ALPHA_8) {
+ return setupModifiers(paint);
+ }
+
+ final ColorFilter filter = paint.getColorFilter();
+ if (filter != null) {
+ nSetupColorFilter(mRenderer, filter.nativeColorFilter);
+ return MODIFIER_COLOR_FILTER;
+ }
+
+ return MODIFIER_NONE;
+ }
+
+ private int setupModifiers(Paint paint) {
+ int modifiers = MODIFIER_NONE;
if (paint.hasShadow) {
nSetupShadow(mRenderer, paint.shadowRadius, paint.shadowDx, paint.shadowDy,
paint.shadowColor);
- hasModifier = true;
+ modifiers |= MODIFIER_SHADOW;
}
final Shader shader = paint.getShader();
if (shader != null) {
nSetupShader(mRenderer, shader.native_shader);
- hasModifier = true;
+ modifiers |= MODIFIER_SHADER;
}
final ColorFilter filter = paint.getColorFilter();
if (filter != null) {
nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- hasModifier = true;
+ modifiers |= MODIFIER_COLOR_FILTER;
}
- return hasModifier;
+ return modifiers;
}
- private boolean setupColorFilter(Paint paint) {
+ private int setupColorFilter(Paint paint) {
final ColorFilter filter = paint.getColorFilter();
if (filter != null) {
nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- return true;
+ return MODIFIER_COLOR_FILTER;
}
- return false;
+ return MODIFIER_NONE;
}
-
+
private static native void nSetupShader(int renderer, int shader);
private static native void nSetupColorFilter(int renderer, int colorFilter);
private static native void nSetupShadow(int renderer, float radius,
float dx, float dy, int color);
- private static native void nResetModifiers(int renderer);
+ private static native void nResetModifiers(int renderer, int modifiers);
}
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 6000a4a..bc191a6 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -14,39 +14,21 @@
* limitations under the License.
*/
-package android.view;
-import android.graphics.Canvas;
+package android.view;
/**
* An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
*/
-class GLES20Layer extends HardwareLayer {
- private int mLayer;
-
- private int mLayerWidth;
- private int mLayerHeight;
-
- private final GLES20Canvas mCanvas;
+abstract class GLES20Layer extends HardwareLayer {
+ int mLayer;
+ Finalizer mFinalizer;
- @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
- private final Finalizer mFinalizer;
-
- GLES20Layer(int width, int height, boolean isOpaque) {
- super(width, height, isOpaque);
-
- int[] layerInfo = new int[2];
- mLayer = GLES20Canvas.nCreateLayer(width, height, isOpaque, layerInfo);
- if (mLayer != 0) {
- mLayerWidth = layerInfo[0];
- mLayerHeight = layerInfo[1];
+ GLES20Layer() {
+ }
- mCanvas = new GLES20Canvas(mLayer, !isOpaque);
- mFinalizer = new Finalizer(mLayer);
- } else {
- mCanvas = null;
- mFinalizer = null;
- }
+ GLES20Layer(int width, int height, boolean opaque) {
+ super(width, height, opaque);
}
/**
@@ -58,55 +40,14 @@ class GLES20Layer extends HardwareLayer {
return mLayer;
}
- @Override
- boolean isValid() {
- return mLayer != 0 && mLayerWidth > 0 && mLayerHeight > 0;
- }
-
- @Override
- void resize(int width, int height) {
- if (!isValid() || width <= 0 || height <= 0) return;
-
- mWidth = width;
- mHeight = height;
-
- if (width != mLayerWidth || height != mLayerHeight) {
- int[] layerInfo = new int[2];
-
- GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo);
-
- mLayerWidth = layerInfo[0];
- mLayerHeight = layerInfo[1];
- }
- }
-
- @Override
- HardwareCanvas getCanvas() {
- return mCanvas;
- }
-
- @Override
- void end(Canvas currentCanvas) {
- if (currentCanvas instanceof GLES20Canvas) {
- ((GLES20Canvas) currentCanvas).resume();
- }
- }
-
- @Override
- HardwareCanvas start(Canvas currentCanvas) {
- if (currentCanvas instanceof GLES20Canvas) {
- ((GLES20Canvas) currentCanvas).interrupt();
- }
- return getCanvas();
- }
-
+
@Override
void destroy() {
- mFinalizer.destroy();
+ if (mFinalizer != null) mFinalizer.destroy();
mLayer = 0;
}
- private static class Finalizer {
+ static class Finalizer {
private int mLayerId;
public Finalizer(int layerId) {
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index 2603281..ec94fe7 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -25,7 +25,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
-import java.util.HashSet;
+import java.util.ArrayList;
/**
* An implementation of a GL canvas that records drawing operations.
@@ -37,7 +37,7 @@ class GLES20RecordingCanvas extends GLES20Canvas {
// These lists ensure that any Bitmaps recorded by a DisplayList are kept alive as long
// as the DisplayList is alive.
@SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
- private final HashSet<Bitmap> mBitmaps = new HashSet<Bitmap>();
+ private final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(5);
GLES20RecordingCanvas(boolean translucent) {
super(true, translucent);
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
new file mode 100644
index 0000000..7adac1c
--- /dev/null
+++ b/core/java/android/view/GLES20RenderLayer.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+
+/**
+ * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This
+ * implementation can be used a rendering target. It generates a
+ * {@link Canvas} that can be used to render into an FBO using OpenGL.
+ */
+class GLES20RenderLayer extends GLES20Layer {
+
+ private int mLayerWidth;
+ private int mLayerHeight;
+
+ private final GLES20Canvas mCanvas;
+
+ GLES20RenderLayer(int width, int height, boolean isOpaque) {
+ super(width, height, isOpaque);
+
+ int[] layerInfo = new int[2];
+ mLayer = GLES20Canvas.nCreateLayer(width, height, isOpaque, layerInfo);
+ if (mLayer != 0) {
+ mLayerWidth = layerInfo[0];
+ mLayerHeight = layerInfo[1];
+
+ mCanvas = new GLES20Canvas(mLayer, !isOpaque);
+ mFinalizer = new Finalizer(mLayer);
+ } else {
+ mCanvas = null;
+ mFinalizer = null;
+ }
+ }
+
+ @Override
+ boolean isValid() {
+ return mLayer != 0 && mLayerWidth > 0 && mLayerHeight > 0;
+ }
+
+ @Override
+ void resize(int width, int height) {
+ if (!isValid() || width <= 0 || height <= 0) return;
+
+ mWidth = width;
+ mHeight = height;
+
+ if (width != mLayerWidth || height != mLayerHeight) {
+ int[] layerInfo = new int[2];
+
+ GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo);
+
+ mLayerWidth = layerInfo[0];
+ mLayerHeight = layerInfo[1];
+ }
+ }
+
+ @Override
+ HardwareCanvas getCanvas() {
+ return mCanvas;
+ }
+
+ @Override
+ void end(Canvas currentCanvas) {
+ if (currentCanvas instanceof GLES20Canvas) {
+ ((GLES20Canvas) currentCanvas).resume();
+ }
+ }
+
+ @Override
+ HardwareCanvas start(Canvas currentCanvas) {
+ if (currentCanvas instanceof GLES20Canvas) {
+ ((GLES20Canvas) currentCanvas).interrupt();
+ }
+ return getCanvas();
+ }
+}
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
new file mode 100644
index 0000000..fcf421b
--- /dev/null
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+import android.graphics.SurfaceTexture;
+
+/**
+ * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This
+ * implementation can be used as a texture. Rendering into this
+ * layer is not controlled by a {@link HardwareCanvas}.
+ */
+class GLES20TextureLayer extends GLES20Layer {
+ private int mTexture;
+ private SurfaceTexture mSurface;
+
+ GLES20TextureLayer() {
+ int[] layerInfo = new int[2];
+ mLayer = GLES20Canvas.nCreateTextureLayer(layerInfo);
+
+ if (mLayer != 0) {
+ mTexture = layerInfo[0];
+ mFinalizer = new Finalizer(mLayer);
+ } else {
+ mFinalizer = null;
+ }
+ }
+
+ @Override
+ boolean isValid() {
+ return mLayer != 0 && mTexture != 0;
+ }
+
+ @Override
+ void resize(int width, int height) {
+ }
+
+ @Override
+ HardwareCanvas getCanvas() {
+ return null;
+ }
+
+ @Override
+ HardwareCanvas start(Canvas currentCanvas) {
+ return null;
+ }
+
+ @Override
+ void end(Canvas currentCanvas) {
+ }
+
+ SurfaceTexture getSurfaceTexture() {
+ if (mSurface == null) {
+ mSurface = new SurfaceTexture(mTexture);
+ }
+ return mSurface;
+ }
+
+ void update(int width, int height, int surface) {
+ GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, surface);
+ }
+}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 79b3d42..a496a9e 100755..100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -193,8 +193,10 @@ public class GestureDetector {
}
}
+ // TODO: ViewConfiguration
+ private int mBiggerTouchSlopSquare = 20 * 20;
+
private int mTouchSlopSquare;
- private int mLargeTouchSlopSquare;
private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
private int mMaximumFlingVelocity;
@@ -243,6 +245,13 @@ public class GestureDetector {
*/
private VelocityTracker mVelocityTracker;
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
private class GestureHandler extends Handler {
GestureHandler() {
super();
@@ -382,11 +391,10 @@ public class GestureDetector {
mIgnoreMultitouch = ignoreMultitouch;
// Fallback to support pre-donuts releases
- int touchSlop, largeTouchSlop, doubleTapSlop;
+ int touchSlop, doubleTapSlop;
if (context == null) {
//noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
- largeTouchSlop = touchSlop + 2;
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
//noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
@@ -394,13 +402,11 @@ public class GestureDetector {
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
- largeTouchSlop = configuration.getScaledLargeTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop;
- mLargeTouchSlopSquare = largeTouchSlop * largeTouchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
@@ -444,6 +450,10 @@ public class GestureDetector {
* else false.
*/
public boolean onTouchEvent(MotionEvent ev) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
+ }
+
final int action = ev.getAction();
final float y = ev.getY();
final float x = ev.getX();
@@ -535,7 +545,7 @@ public class GestureDetector {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
- if (distance > mLargeTouchSlopSquare) {
+ if (distance > mBiggerTouchSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
@@ -580,8 +590,14 @@ public class GestureDetector {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
+
case MotionEvent.ACTION_CANCEL:
cancel();
+ break;
+ }
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
@@ -594,6 +610,8 @@ public class GestureDetector {
mVelocityTracker = null;
mIsDoubleTapping = false;
mStillDown = false;
+ mAlwaysInTapRegion = false;
+ mAlwaysInBiggerTapRegion = false;
if (mInLongPress) {
mInLongPress = false;
}
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index cf79638..69e6489 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -80,9 +80,14 @@ public class Gravity
/** Flag to clip the edges of the object to its container along the
* horizontal axis. */
public static final int CLIP_HORIZONTAL = AXIS_CLIP<<AXIS_X_SHIFT;
-
+
+ /** Raw bit controlling whether the layout direction is relative or not (START/END instead of
+ * absolute LEFT/RIGHT).
+ */
+ public static final int RELATIVE_LAYOUT_DIRECTION = 0x00800000;
+
/**
- * Binary mask to get the horizontal gravity of a gravity.
+ * Binary mask to get the absolute horizontal gravity of a gravity.
*/
public static final int HORIZONTAL_GRAVITY_MASK = (AXIS_SPECIFIED |
AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_X_SHIFT;
@@ -106,8 +111,19 @@ public class Gravity
*/
public static final int DISPLAY_CLIP_HORIZONTAL = 0x01000000;
+ /** Push object to x-axis position at the start of its container, not changing its size. */
+ public static final int START = RELATIVE_LAYOUT_DIRECTION | LEFT;
+
+ /** Push object to x-axis position at the end of its container, not changing its size. */
+ public static final int END = RELATIVE_LAYOUT_DIRECTION | RIGHT;
+
/**
- * Apply a gravity constant to an object.
+ * Binary mask for the horizontal gravity and script specific direction bit.
+ */
+ public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = START | END;
+
+ /**
+ * Apply a gravity constant to an object. This suppose that the layout direction is LTR.
*
* @param gravity The desired placement of the object, as defined by the
* constants in this class.
@@ -119,12 +135,33 @@ public class Gravity
* @param outRect Receives the computed frame of the object in its
* container.
*/
- public static void apply(int gravity, int w, int h, Rect container,
- Rect outRect) {
+ public static void apply(int gravity, int w, int h, Rect container, Rect outRect) {
apply(gravity, w, h, container, 0, 0, outRect);
}
/**
+ * Apply a gravity constant to an object and take care if layout direction is RTL or not.
+ *
+ * @param gravity The desired placement of the object, as defined by the
+ * constants in this class.
+ * @param w The horizontal size of the object.
+ * @param h The vertical size of the object.
+ * @param container The frame of the containing space, in which the object
+ * will be placed. Should be large enough to contain the
+ * width and height of the object.
+ * @param outRect Receives the computed frame of the object in its
+ * container.
+ * @param isRtl Whether the layout is right-to-left.
+ *
+ * @hide
+ */
+ public static void apply(int gravity, int w, int h, Rect container,
+ Rect outRect, boolean isRtl) {
+ int absGravity = getAbsoluteGravity(gravity, isRtl);
+ apply(absGravity, w, h, container, 0, 0, outRect);
+ }
+
+ /**
* Apply a gravity constant to an object.
*
* @param gravity The desired placement of the object, as defined by the
@@ -146,7 +183,7 @@ public class Gravity
* container.
*/
public static void apply(int gravity, int w, int h, Rect container,
- int xAdj, int yAdj, Rect outRect) {
+ int xAdj, int yAdj, Rect outRect) {
switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
case 0:
outRect.left = container.left
@@ -301,6 +338,48 @@ public class Gravity
* @return true if the supplied gravity has an horizontal pull
*/
public static boolean isHorizontal(int gravity) {
- return gravity > 0 && (gravity & HORIZONTAL_GRAVITY_MASK) != 0;
+ return gravity > 0 && (gravity & RELATIVE_HORIZONTAL_GRAVITY_MASK) != 0;
+ }
+
+ /**
+ * <p>Convert script specific gravity to absolute horizontal value.</p>
+ *
+ * if horizontal direction is LTR, then START will set LEFT and END will set RIGHT.
+ * if horizontal direction is RTL, then START will set RIGHT and END will set LEFT.
+ *
+ * @param gravity The gravity to convert to absolute (horizontal) values.
+ * @param isRtl Whether the layout is right-to-left.
+ * @return gravity converted to absolute (horizontal) values.
+ */
+ public static int getAbsoluteGravity(int gravity, boolean isRtl) {
+ int result = gravity;
+ // If layout is script specific and gravity is horizontal relative (START or END)
+ if ((result & RELATIVE_LAYOUT_DIRECTION) > 0) {
+ if ((result & Gravity.START) == Gravity.START) {
+ // Remove the START bit
+ result &= ~START;
+ if (isRtl) {
+ // Set the RIGHT bit
+ result |= RIGHT;
+ } else {
+ // Set the LEFT bit
+ result |= LEFT;
+ }
+ } else if ((result & Gravity.END) == Gravity.END) {
+ // Remove the END bit
+ result &= ~END;
+ if (isRtl) {
+ // Set the LEFT bit
+ result |= LEFT;
+ } else {
+ // Set the RIGHT bit
+ result |= RIGHT;
+ }
+ }
+ // Don't need the script specific bit any more, so remove it as we are converting to
+ // absolute values (LEFT or RIGHT)
+ result &= ~RELATIVE_LAYOUT_DIRECTION;
+ }
+ return result;
}
}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index caa7b74..23b3abc 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -64,6 +64,14 @@ public abstract class HardwareCanvas extends Canvas {
abstract boolean drawDisplayList(DisplayList displayList, int width, int height, Rect dirty);
/**
+ * Outputs the specified display list to the log. This method exists for use by
+ * tools to output display lists for selected nodes to the log.
+ *
+ * @param displayList The display list to be logged.
+ */
+ abstract void outputDisplayList(DisplayList displayList);
+
+ /**
* Draws the specified layer onto this canvas.
*
* @param layer The layer to composite on this canvas
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index d01b8ce..86dec3f 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -26,12 +26,24 @@ import android.graphics.Canvas;
* drawn several times.
*/
abstract class HardwareLayer {
+ /**
+ * Indicates an unknown dimension (width or height.)
+ */
+ static final int DIMENSION_UNDEFINED = -1;
+
int mWidth;
int mHeight;
final boolean mOpaque;
/**
+ * Creates a new hardware layer with undefined dimensions.
+ */
+ HardwareLayer() {
+ this(DIMENSION_UNDEFINED, DIMENSION_UNDEFINED, false);
+ }
+
+ /**
* Creates a new hardware layer at least as large as the supplied
* dimensions.
*
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 8584bf2..2611ec0 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,10 +17,10 @@
package android.view;
-import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.os.SystemClock;
+import android.graphics.SurfaceTexture;
+import android.os.*;
import android.util.EventLog;
import android.util.Log;
@@ -33,7 +33,7 @@ import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
/**
- * Interface for rendering a ViewRoot using hardware acceleration.
+ * Interface for rendering a ViewAncestor using hardware acceleration.
*
* @hide
*/
@@ -136,14 +136,14 @@ public abstract class HardwareRenderer {
*
* @param canvas The Canvas used to render the view.
*/
- void onHardwarePreDraw(Canvas canvas);
+ void onHardwarePreDraw(HardwareCanvas canvas);
/**
* Invoked after a view is drawn by a hardware renderer.
*
* @param canvas The Canvas used to render the view.
*/
- void onHardwarePostDraw(Canvas canvas);
+ void onHardwarePostDraw(HardwareCanvas canvas);
}
/**
@@ -166,6 +166,14 @@ public abstract class HardwareRenderer {
abstract DisplayList createDisplayList(View v);
/**
+ * Creates a new hardware layer. A hardware layer built by calling this
+ * method will be treated as a texture layer, instead of as a render target.
+ *
+ * @return A hardware layer
+ */
+ abstract HardwareLayer createHardwareLayer();
+
+ /**
* Creates a new hardware layer.
*
* @param width The minimum width of the layer
@@ -175,10 +183,32 @@ public abstract class HardwareRenderer {
* @return A hardware layer
*/
abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
+
+ /**
+ * 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}
+ *
+ * @return A {@link SurfaceTexture}
+ */
+ abstract SurfaceTexture createSuraceTexture(HardwareLayer layer);
+
+ /**
+ * Updates the specified layer.
+ *
+ * @param layer The hardware layer to update
+ * @param width The layer's width
+ * @param height The layer's height
+ * @param surface The surface to update
+ */
+ abstract void updateTextureLayer(HardwareLayer layer, int width, int height,
+ SurfaceTexture surface);
/**
* Initializes the hardware renderer for the specified surface and setup the
- * renderer for drawing, if needed. This is invoked when the ViewRoot has
+ * renderer for drawing, if needed. This is invoked when the ViewAncestor has
* potentially lost the hardware renderer. The hardware renderer should be
* reinitialized and setup when the render {@link #isRequested()} and
* {@link #isEnabled()}.
@@ -256,9 +286,11 @@ public abstract class HardwareRenderer {
@SuppressWarnings({"deprecation"})
static abstract class GlRenderer extends HardwareRenderer {
- private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- private static final int EGL_SURFACE_TYPE = 0x3033;
- private static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
+ // These values are not exposed in our EGL APIs
+ static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ static final int EGL_SURFACE_TYPE = 0x3033;
+ static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
+ static final int EGL_OPENGL_ES2_BIT = 4;
private static final int SURFACE_STATE_ERROR = 0;
private static final int SURFACE_STATE_SUCCESS = 1;
@@ -279,6 +311,7 @@ public abstract class HardwareRenderer {
Paint mDebugPaint;
boolean mDirtyRegions;
+ final boolean mDirtyRegionsRequested;
final int mGlVersion;
final boolean mTranslucent;
@@ -290,9 +323,10 @@ public abstract class HardwareRenderer {
GlRenderer(int glVersion, boolean translucent) {
mGlVersion = glVersion;
mTranslucent = translucent;
- final String dirtyProperty = System.getProperty(RENDER_DIRTY_REGIONS_PROPERTY, "true");
+ final String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
//noinspection PointlessBooleanExpression,ConstantConditions
mDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty);
+ mDirtyRegionsRequested = mDirtyRegions;
}
/**
@@ -426,13 +460,12 @@ public abstract class HardwareRenderer {
getEGLErrorString(sEgl.eglGetError()));
}
- sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay);
+ sEglConfig = chooseEglConfig();
if (sEglConfig == null) {
// We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
if (mDirtyRegions) {
mDirtyRegions = false;
-
- sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay);
+ sEglConfig = chooseEglConfig();
if (sEglConfig == null) {
throw new RuntimeException("eglConfig not initialized");
}
@@ -448,6 +481,21 @@ public abstract class HardwareRenderer {
sEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
}
+ private EGLConfig chooseEglConfig() {
+ int[] configsCount = new int[1];
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] configSpec = getConfig(mDirtyRegions);
+ if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
+ throw new IllegalArgumentException("eglChooseConfig failed " +
+ getEGLErrorString(sEgl.eglGetError()));
+ } else if (configsCount[0] > 0) {
+ return configs[0];
+ }
+ return null;
+ }
+
+ abstract int[] getConfig(boolean dirtyRegions);
+
GL createEglSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
// Check preconditions.
if (sEgl == null) {
@@ -499,11 +547,21 @@ public abstract class HardwareRenderer {
throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
+ getEGLErrorString(sEgl.eglGetError()));
}
-
+
+ // If mDirtyRegions is set, this means we have an EGL configuration
+ // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
if (mDirtyRegions) {
if (!GLES20Canvas.preserveBackBuffer()) {
Log.w(LOG_TAG, "Backbuffer cannot be preserved");
}
+ } else if (mDirtyRegionsRequested) {
+ // If mDirtyRegions is not set, our EGL configuration does not
+ // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
+ // swap behavior might be EGL_BUFFER_PRESERVED, which means we
+ // want to set mDirtyRegions. We try to do this only if dirty
+ // regions were initially requested as part of the device
+ // configuration (see RENDER_DIRTY_REGIONS)
+ mDirtyRegions = GLES20Canvas.isBackBufferPreserved();
}
return sEglContext.getGL();
@@ -559,15 +617,6 @@ public abstract class HardwareRenderer {
void onPostDraw() {
}
-
- /**
- * Defines the EGL configuration for this renderer.
- *
- * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}.
- */
- EglConfigChooser getConfigChooser(int glVersion) {
- return new ComponentSizeChooser(glVersion, 8, 8, 8, 8, 0, 0, mDirtyRegions);
- }
@Override
void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
@@ -644,8 +693,21 @@ public abstract class HardwareRenderer {
}
attachInfo.mIgnoreDirtyState = false;
-
+
+ final long swapBuffersStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ swapBuffersStartTime = System.nanoTime();
+ }
+
sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
+
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(LOG_TAG, "Latency: Spent "
+ + ((now - swapBuffersStartTime) * 0.000001f)
+ + "ms waiting for eglSwapBuffers()");
+ }
+
checkEglErrors();
}
}
@@ -667,134 +729,6 @@ public abstract class HardwareRenderer {
}
return SURFACE_STATE_SUCCESS;
}
-
- static abstract class EglConfigChooser {
- final int[] mConfigSpec;
- private final int mGlVersion;
-
- EglConfigChooser(int glVersion, int[] configSpec) {
- mGlVersion = glVersion;
- mConfigSpec = filterConfigSpec(configSpec);
- }
-
- EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
- int[] index = new int[1];
- if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
- throw new IllegalArgumentException("eglChooseConfig failed "
- + getEGLErrorString(egl.eglGetError()));
- }
-
- int numConfigs = index[0];
- if (numConfigs <= 0) {
- throw new IllegalArgumentException("No configs match configSpec");
- }
-
- EGLConfig[] configs = new EGLConfig[numConfigs];
- if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
- throw new IllegalArgumentException("eglChooseConfig failed "
- + getEGLErrorString(egl.eglGetError()));
- }
-
- EGLConfig config = chooseConfig(egl, display, configs);
- if (config == null) {
- throw new IllegalArgumentException("No config chosen");
- }
-
- return config;
- }
-
- abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);
-
- private int[] filterConfigSpec(int[] configSpec) {
- if (mGlVersion != 2) {
- return configSpec;
- }
- /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
- * And we know the configSpec is well formed.
- */
- int len = configSpec.length;
- int[] newConfigSpec = new int[len + 2];
- System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);
- newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;
- newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
- newConfigSpec[len + 1] = EGL10.EGL_NONE;
- return newConfigSpec;
- }
- }
-
- /**
- * Choose a configuration with exactly the specified r,g,b,a sizes,
- * and at least the specified depth and stencil sizes.
- */
- static class ComponentSizeChooser extends EglConfigChooser {
- private int[] mValue;
-
- private final int mRedSize;
- private final int mGreenSize;
- private final int mBlueSize;
- private final int mAlphaSize;
- private final int mDepthSize;
- private final int mStencilSize;
- private final boolean mDirtyRegions;
-
- ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
- int alphaSize, int depthSize, int stencilSize, boolean dirtyRegions) {
- super(glVersion, new int[] {
- EGL10.EGL_RED_SIZE, redSize,
- EGL10.EGL_GREEN_SIZE, greenSize,
- EGL10.EGL_BLUE_SIZE, blueSize,
- EGL10.EGL_ALPHA_SIZE, alphaSize,
- EGL10.EGL_DEPTH_SIZE, depthSize,
- EGL10.EGL_STENCIL_SIZE, stencilSize,
- EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT |
- (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
- EGL10.EGL_NONE });
- mValue = new int[1];
- mRedSize = redSize;
- mGreenSize = greenSize;
- mBlueSize = blueSize;
- mAlphaSize = alphaSize;
- mDepthSize = depthSize;
- mStencilSize = stencilSize;
- mDirtyRegions = dirtyRegions;
- }
-
- @Override
- EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
- for (EGLConfig config : configs) {
- int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
- int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
- if (d >= mDepthSize && s >= mStencilSize) {
- int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
- int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
- int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
- int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
- boolean backBuffer;
- if (mDirtyRegions) {
- int surfaceType = findConfigAttrib(egl, display, config,
- EGL_SURFACE_TYPE, 0);
- backBuffer = (surfaceType & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) != 0;
- } else {
- backBuffer = true;
- }
- if (r >= mRedSize && g >= mGreenSize && b >= mBlueSize && a >= mAlphaSize
- && backBuffer) {
- return config;
- }
- }
- }
- return null;
- }
-
- private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
- int attribute, int defaultValue) {
- if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
- return mValue[0];
- }
-
- return defaultValue;
- }
- }
}
/**
@@ -811,7 +745,23 @@ public abstract class HardwareRenderer {
GLES20Canvas createCanvas() {
return mGlCanvas = new GLES20Canvas(mTranslucent);
}
-
+
+ @Override
+ int[] getConfig(boolean dirtyRegions) {
+ return new int[] {
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_RED_SIZE, 8,
+ EGL10.EGL_GREEN_SIZE, 8,
+ EGL10.EGL_BLUE_SIZE, 8,
+ EGL10.EGL_ALPHA_SIZE, 8,
+ EGL10.EGL_DEPTH_SIZE, 0,
+ EGL10.EGL_STENCIL_SIZE, 0,
+ EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT |
+ (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
+ EGL10.EGL_NONE
+ };
+ }
+
@Override
boolean canDraw() {
return super.canDraw() && mGlCanvas != null;
@@ -842,10 +792,26 @@ public abstract class HardwareRenderer {
DisplayList createDisplayList(View v) {
return new GLES20DisplayList(v);
}
-
+
+ @Override
+ HardwareLayer createHardwareLayer() {
+ return new GLES20TextureLayer();
+ }
+
@Override
HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
- return new GLES20Layer(width, height, isOpaque);
+ return new GLES20RenderLayer(width, height, isOpaque);
+ }
+
+ @Override
+ SurfaceTexture createSuraceTexture(HardwareLayer layer) {
+ return ((GLES20TextureLayer) layer).getSurfaceTexture();
+ }
+
+ @Override
+ void updateTextureLayer(HardwareLayer layer, int width, int height,
+ SurfaceTexture surface) {
+ ((GLES20TextureLayer) layer).update(width, height, surface.mSurfaceTexture);
}
static HardwareRenderer create(boolean translucent) {
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 8cb68f9..bfc7c31 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -153,7 +153,14 @@ public final class InputDevice implements Parcelable {
* @see #SOURCE_CLASS_POINTER
*/
public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER;
-
+
+ /**
+ * The input source is a stylus pointing device.
+ *
+ * @see #SOURCE_CLASS_POINTER
+ */
+ public static final int SOURCE_STYLUS = 0x00004000 | SOURCE_CLASS_POINTER;
+
/**
* The input source is a trackball.
*
@@ -585,6 +592,7 @@ public final class InputDevice implements Parcelable {
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHSCREEN, "touchscreen");
appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
+ appendSourceDescriptionIfApplicable(description, SOURCE_STYLUS, "stylus");
appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK, "joystick");
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index f6aeb39..01ddcc9 100755
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -67,6 +67,52 @@ public abstract class InputEvent implements Parcelable {
*/
public abstract void setSource(int source);
+ /**
+ * Copies the event.
+ *
+ * @return A deep copy of the event.
+ * @hide
+ */
+ public abstract InputEvent copy();
+
+ /**
+ * Recycles the event.
+ * This method should only be used by the system since applications do not
+ * expect {@link KeyEvent} objects to be recycled, although {@link MotionEvent}
+ * objects are fine. See {@link KeyEvent#recycle()} for details.
+ * @hide
+ */
+ public abstract void recycle();
+
+ /**
+ * Gets a private flag that indicates when the system has detected that this input event
+ * may be inconsistent with respect to the sequence of previously delivered input events,
+ * such as when a key up event is sent but the key was not down or when a pointer
+ * move event is sent but the pointer is not down.
+ *
+ * @return True if this event is tainted.
+ * @hide
+ */
+ public abstract boolean isTainted();
+
+ /**
+ * Sets a private flag that indicates when the system has detected that this input event
+ * may be inconsistent with respect to the sequence of previously delivered input events,
+ * such as when a key up event is sent but the key was not down or when a pointer
+ * move event is sent but the pointer is not down.
+ *
+ * @param tainted True if this event is tainted.
+ * @hide
+ */
+ public abstract void setTainted(boolean tainted);
+
+ /**
+ * Returns the time (in ns) when this specific event was generated.
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ * @hide
+ */
+ public abstract long getEventTimeNano();
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
new file mode 100644
index 0000000..e14b975
--- /dev/null
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -0,0 +1,730 @@
+/*
+ * 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.view;
+
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * Checks whether a sequence of input events is self-consistent.
+ * Logs a description of each problem detected.
+ * <p>
+ * When a problem is detected, the event is tainted. This mechanism prevents the same
+ * error from being reported multiple times.
+ * </p>
+ *
+ * @hide
+ */
+public final class InputEventConsistencyVerifier {
+ private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
+
+ // The number of recent events to log when a problem is detected.
+ // Can be set to 0 to disable logging recent events but the runtime overhead of
+ // this feature is negligible on current hardware.
+ private static final int RECENT_EVENTS_TO_LOG = 5;
+
+ // The object to which the verifier is attached.
+ private final Object mCaller;
+
+ // Consistency verifier flags.
+ private final int mFlags;
+
+ // Tag for logging which a client can set to help distinguish the output
+ // from different verifiers since several can be active at the same time.
+ // If not provided defaults to the simple class name.
+ private final String mLogTag;
+
+ // The most recently checked event and the nesting level at which it was checked.
+ // This is only set when the verifier is called from a nesting level greater than 0
+ // so that the verifier can detect when it has been asked to verify the same event twice.
+ // It does not make sense to examine the contents of the last event since it may have
+ // been recycled.
+ private InputEvent mLastEvent;
+ private int mLastNestingLevel;
+
+ // Copy of the most recent events.
+ private InputEvent[] mRecentEvents;
+ private boolean[] mRecentEventsUnhandled;
+ private int mMostRecentEventIndex;
+
+ // Current event and its type.
+ private InputEvent mCurrentEvent;
+ private String mCurrentEventType;
+
+ // Linked list of key state objects.
+ private KeyState mKeyStateList;
+
+ // Current state of the trackball.
+ private boolean mTrackballDown;
+ private boolean mTrackballUnhandled;
+
+ // Bitfield of pointer ids that are currently down.
+ // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
+ // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ private int mTouchEventStreamPointers;
+
+ // The device id and source of the current stream of touch events.
+ private int mTouchEventStreamDeviceId = -1;
+ private int mTouchEventStreamSource;
+
+ // Set to true when we discover that the touch event stream is inconsistent.
+ // Reset on down or cancel.
+ private boolean mTouchEventStreamIsTainted;
+
+ // Set to true if the touch event stream is partially unhandled.
+ private boolean mTouchEventStreamUnhandled;
+
+ // Set to true if we received hover enter.
+ private boolean mHoverEntered;
+
+ // The current violation message.
+ private StringBuilder mViolationMessage;
+
+ /**
+ * Indicates that the verifier is intended to act on raw device input event streams.
+ * Disables certain checks for invariants that are established by the input dispatcher
+ * itself as it delivers input events, such as key repeating behavior.
+ */
+ public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0;
+
+ /**
+ * Creates an input consistency verifier.
+ * @param caller The object to which the verifier is attached.
+ * @param flags Flags to the verifier, or 0 if none.
+ */
+ public InputEventConsistencyVerifier(Object caller, int flags) {
+ this(caller, flags, InputEventConsistencyVerifier.class.getSimpleName());
+ }
+
+ /**
+ * Creates an input consistency verifier.
+ * @param caller The object to which the verifier is attached.
+ * @param flags Flags to the verifier, or 0 if none.
+ * @param logTag Tag for logging. If null defaults to the short class name.
+ */
+ public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
+ this.mCaller = caller;
+ this.mFlags = flags;
+ this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
+ }
+
+ /**
+ * Determines whether the instrumentation should be enabled.
+ * @return True if it should be enabled.
+ */
+ public static boolean isInstrumentationEnabled() {
+ return IS_ENG_BUILD;
+ }
+
+ /**
+ * Resets the state of the input event consistency verifier.
+ */
+ public void reset() {
+ mLastEvent = null;
+ mLastNestingLevel = 0;
+ mTrackballDown = false;
+ mTrackballUnhandled = false;
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mHoverEntered = false;
+
+ while (mKeyStateList != null) {
+ final KeyState state = mKeyStateList;
+ mKeyStateList = state.next;
+ state.recycle();
+ }
+ }
+
+ /**
+ * Checks an arbitrary input event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onInputEvent(InputEvent event, int nestingLevel) {
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ onKeyEvent(keyEvent, nestingLevel);
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ onTouchEvent(motionEvent, nestingLevel);
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ onTrackballEvent(motionEvent, nestingLevel);
+ } else {
+ onGenericMotionEvent(motionEvent, nestingLevel);
+ }
+ }
+ }
+
+ /**
+ * Checks a key event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onKeyEvent(KeyEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "KeyEvent")) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+ final int keyCode = event.getKeyCode();
+ switch (action) {
+ case KeyEvent.ACTION_DOWN: {
+ KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ // If the key is already down, ensure it is a repeat.
+ // We don't perform this check when processing raw device input
+ // because the input dispatcher itself is responsible for setting
+ // the key repeat count before it delivers input events.
+ if (state.unhandled) {
+ state.unhandled = false;
+ } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+ && event.getRepeatCount() == 0) {
+ problem("ACTION_DOWN but key is already down and this event "
+ + "is not a key repeat.");
+ }
+ } else {
+ addKeyState(deviceId, source, keyCode);
+ }
+ break;
+ }
+ case KeyEvent.ACTION_UP: {
+ KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true);
+ if (state == null) {
+ problem("ACTION_UP but key was not down.");
+ } else {
+ state.recycle();
+ }
+ break;
+ }
+ case KeyEvent.ACTION_MULTIPLE:
+ break;
+ default:
+ problem("Invalid action " + KeyEvent.actionToString(action)
+ + " for key event.");
+ break;
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Checks a trackball event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onTrackballEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "TrackballEvent")) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mTrackballDown && !mTrackballUnhandled) {
+ problem("ACTION_DOWN but trackball is already down.");
+ } else {
+ mTrackballDown = true;
+ mTrackballUnhandled = false;
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ if (!mTrackballDown) {
+ problem("ACTION_UP but trackball is not down.");
+ } else {
+ mTrackballDown = false;
+ mTrackballUnhandled = false;
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action " + MotionEvent.actionToString(action)
+ + " for trackball event.");
+ break;
+ }
+
+ if (mTrackballDown && event.getPressure() <= 0) {
+ problem("Trackball is down but pressure is not greater than 0.");
+ } else if (!mTrackballDown && event.getPressure() != 0) {
+ problem("Trackball is up but pressure is not equal to 0.");
+ }
+ } else {
+ problem("Source was not SOURCE_CLASS_TRACKBALL.");
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Checks a touch event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onTouchEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "TouchEvent")) {
+ return;
+ }
+
+ final int action = event.getAction();
+ final boolean newStream = action == MotionEvent.ACTION_DOWN
+ || action == MotionEvent.ACTION_CANCEL;
+ if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
+ if (newStream) {
+ mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mTouchEventStreamPointers = 0;
+ } else {
+ finishEvent(mTouchEventStreamIsTainted);
+ return;
+ }
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+
+ if (!newStream && mTouchEventStreamDeviceId != -1
+ && (mTouchEventStreamDeviceId != deviceId
+ || mTouchEventStreamSource != source)) {
+ problem("Touch event stream contains events from multiple sources: "
+ + "previous device id " + mTouchEventStreamDeviceId
+ + ", previous source " + Integer.toHexString(mTouchEventStreamSource)
+ + ", new device id " + deviceId
+ + ", new source " + Integer.toHexString(source));
+ }
+ mTouchEventStreamDeviceId = deviceId;
+ mTouchEventStreamSource = source;
+
+ final int pointerCount = event.getPointerCount();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mTouchEventStreamPointers != 0) {
+ problem("ACTION_DOWN but pointers are already down. "
+ + "Probably missing ACTION_UP from previous gesture.");
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamPointers = 1 << event.getPointerId(0);
+ break;
+ case MotionEvent.ACTION_UP:
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ break;
+ case MotionEvent.ACTION_MOVE: {
+ final int expectedPointerCount =
+ Integer.bitCount(mTouchEventStreamPointers);
+ if (pointerCount != expectedPointerCount) {
+ problem("ACTION_MOVE contained " + pointerCount
+ + " pointers but there are currently "
+ + expectedPointerCount + " pointers down.");
+ mTouchEventStreamIsTainted = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ break;
+ case MotionEvent.ACTION_OUTSIDE:
+ if (mTouchEventStreamPointers != 0) {
+ problem("ACTION_OUTSIDE but pointers are still down.");
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamIsTainted = false;
+ break;
+ default: {
+ final int actionMasked = event.getActionMasked();
+ final int actionIndex = event.getActionIndex();
+ if (actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mTouchEventStreamPointers == 0) {
+ problem("ACTION_POINTER_DOWN but no other pointers were down.");
+ mTouchEventStreamIsTainted = true;
+ }
+ if (actionIndex < 0 || actionIndex >= pointerCount) {
+ problem("ACTION_POINTER_DOWN index is " + actionIndex
+ + " but the pointer count is " + pointerCount + ".");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ final int id = event.getPointerId(actionIndex);
+ final int idBit = 1 << id;
+ if ((mTouchEventStreamPointers & idBit) != 0) {
+ problem("ACTION_POINTER_DOWN specified pointer id " + id
+ + " which is already down.");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ mTouchEventStreamPointers |= idBit;
+ }
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ if (actionIndex < 0 || actionIndex >= pointerCount) {
+ problem("ACTION_POINTER_UP index is " + actionIndex
+ + " but the pointer count is " + pointerCount + ".");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ final int id = event.getPointerId(actionIndex);
+ final int idBit = 1 << id;
+ if ((mTouchEventStreamPointers & idBit) == 0) {
+ problem("ACTION_POINTER_UP specified pointer id " + id
+ + " which is not currently down.");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ mTouchEventStreamPointers &= ~idBit;
+ }
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ } else {
+ problem("Invalid action " + MotionEvent.actionToString(action)
+ + " for touch event.");
+ }
+ break;
+ }
+ }
+ } else {
+ problem("Source was not SOURCE_CLASS_POINTER.");
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Checks a generic motion event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "GenericMotionEvent")) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ ensurePointerCountIsOneForThisAction(event);
+ mHoverEntered = true;
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ ensurePointerCountIsOneForThisAction(event);
+ if (!mHoverEntered) {
+ problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER");
+ }
+ mHoverEntered = false;
+ break;
+ case MotionEvent.ACTION_SCROLL:
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action for generic pointer event.");
+ break;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action for generic joystick event.");
+ break;
+ }
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Notifies the verifier that a given event was unhandled and the rest of the
+ * trace for the event should be ignored.
+ * This method should only be called if the event was previously checked by
+ * the consistency verifier using {@link #onInputEvent} and other methods.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+ if (nestingLevel != mLastNestingLevel) {
+ return;
+ }
+
+ if (mRecentEventsUnhandled != null) {
+ mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+ }
+
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ final int deviceId = keyEvent.getDeviceId();
+ final int source = keyEvent.getSource();
+ final int keyCode = keyEvent.getKeyCode();
+ final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ state.unhandled = true;
+ }
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ mTouchEventStreamUnhandled = true;
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (mTrackballDown) {
+ mTrackballUnhandled = true;
+ }
+ }
+ }
+ }
+
+ private void ensureMetaStateIsNormalized(int metaState) {
+ final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
+ if (normalizedMetaState != metaState) {
+ problem(String.format("Metastate not normalized. Was 0x%08x but expected 0x%08x.",
+ metaState, normalizedMetaState));
+ }
+ }
+
+ private void ensurePointerCountIsOneForThisAction(MotionEvent event) {
+ final int pointerCount = event.getPointerCount();
+ if (pointerCount != 1) {
+ problem("Pointer count is " + pointerCount + " but it should always be 1 for "
+ + MotionEvent.actionToString(event.getAction()));
+ }
+ }
+
+ private void ensureHistorySizeIsZeroForThisAction(MotionEvent event) {
+ final int historySize = event.getHistorySize();
+ if (historySize != 0) {
+ problem("History size is " + historySize + " but it should always be 0 for "
+ + MotionEvent.actionToString(event.getAction()));
+ }
+ }
+
+ private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
+ // Ignore the event if it is already tainted.
+ if (event.isTainted()) {
+ return false;
+ }
+
+ // Ignore the event if we already checked it at a higher nesting level.
+ if (event == mLastEvent && nestingLevel < mLastNestingLevel) {
+ return false;
+ }
+
+ if (nestingLevel > 0) {
+ mLastEvent = event;
+ mLastNestingLevel = nestingLevel;
+ } else {
+ mLastEvent = null;
+ mLastNestingLevel = 0;
+ }
+
+ mCurrentEvent = event;
+ mCurrentEventType = eventType;
+ return true;
+ }
+
+ private void finishEvent(boolean tainted) {
+ if (mViolationMessage != null && mViolationMessage.length() != 0) {
+ mViolationMessage.append("\n in ").append(mCaller);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, 0, mCurrentEvent, false);
+
+ if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
+ mViolationMessage.append("\n -- recent events --");
+ for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
+ final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
+ % RECENT_EVENTS_TO_LOG;
+ final InputEvent event = mRecentEvents[index];
+ if (event == null) {
+ break;
+ }
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
+ }
+ }
+
+ Log.d(mLogTag, mViolationMessage.toString());
+ mViolationMessage.setLength(0);
+ tainted = true;
+ }
+
+ if (tainted) {
+ // Taint the event so that we do not generate additional violations from it
+ // further downstream.
+ mCurrentEvent.setTainted(true);
+ }
+
+ if (RECENT_EVENTS_TO_LOG != 0) {
+ if (mRecentEvents == null) {
+ mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+ mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
+ }
+ final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
+ mMostRecentEventIndex = index;
+ if (mRecentEvents[index] != null) {
+ mRecentEvents[index].recycle();
+ }
+ mRecentEvents[index] = mCurrentEvent.copy();
+ mRecentEventsUnhandled[index] = false;
+ }
+
+ mCurrentEvent = null;
+ mCurrentEventType = null;
+ }
+
+ private static void appendEvent(StringBuilder message, int index,
+ InputEvent event, boolean unhandled) {
+ message.append(index).append(": sent at ").append(event.getEventTimeNano());
+ message.append(", ");
+ if (unhandled) {
+ message.append("(unhandled) ");
+ }
+ message.append(event);
+ }
+
+ private void problem(String message) {
+ if (mViolationMessage == null) {
+ mViolationMessage = new StringBuilder();
+ }
+ if (mViolationMessage.length() == 0) {
+ mViolationMessage.append(mCurrentEventType).append(": ");
+ } else {
+ mViolationMessage.append("\n ");
+ }
+ mViolationMessage.append(message);
+ }
+
+ private KeyState findKeyState(int deviceId, int source, int keyCode, boolean remove) {
+ KeyState last = null;
+ KeyState state = mKeyStateList;
+ while (state != null) {
+ if (state.deviceId == deviceId && state.source == source
+ && state.keyCode == keyCode) {
+ if (remove) {
+ if (last != null) {
+ last.next = state.next;
+ } else {
+ mKeyStateList = state.next;
+ }
+ state.next = null;
+ }
+ return state;
+ }
+ last = state;
+ state = state.next;
+ }
+ return null;
+ }
+
+ private void addKeyState(int deviceId, int source, int keyCode) {
+ KeyState state = KeyState.obtain(deviceId, source, keyCode);
+ state.next = mKeyStateList;
+ mKeyStateList = state;
+ }
+
+ private static final class KeyState {
+ private static Object mRecycledListLock = new Object();
+ private static KeyState mRecycledList;
+
+ public KeyState next;
+ public int deviceId;
+ public int source;
+ public int keyCode;
+ public boolean unhandled;
+
+ private KeyState() {
+ }
+
+ public static KeyState obtain(int deviceId, int source, int keyCode) {
+ KeyState state;
+ synchronized (mRecycledListLock) {
+ state = mRecycledList;
+ if (state != null) {
+ mRecycledList = state.next;
+ } else {
+ state = new KeyState();
+ }
+ }
+ state.deviceId = deviceId;
+ state.source = source;
+ state.keyCode = keyCode;
+ state.unhandled = false;
+ return state;
+ }
+
+ public void recycle() {
+ synchronized (mRecycledListLock) {
+ next = mRecycledList;
+ mRecycledList = next;
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index f455f91..5dbda90 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -566,6 +566,19 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static final int KEYCODE_BUTTON_15 = 202;
/** Key code constant: Generic Game Pad Button #16.*/
public static final int KEYCODE_BUTTON_16 = 203;
+ /** Key code constant: Language Switch key.
+ * Toggles the current input language such as switching between English and Japanese on
+ * a QWERTY keyboard. On some devices, the same function may be performed by
+ * pressing Shift+Spacebar. */
+ public static final int KEYCODE_LANGUAGE_SWITCH = 204;
+ /** Key code constant: Manner Mode key.
+ * Toggles silent or vibrate mode on and off to make the device behave more politely
+ * in certain settings such as on a crowded train. On some devices, the key may only
+ * operate when long-pressed. */
+ public static final int KEYCODE_MANNER_MODE = 205;
+ /** Key code constant: 3D Mode key.
+ * Toggles the display between 2D and 3D mode. */
+ public static final int KEYCODE_3D_MODE = 206;
private static final int LAST_KEYCODE = KEYCODE_BUTTON_16;
@@ -791,6 +804,9 @@ public class KeyEvent extends InputEvent implements Parcelable {
names.append(KEYCODE_BUTTON_14, "KEYCODE_BUTTON_14");
names.append(KEYCODE_BUTTON_15, "KEYCODE_BUTTON_15");
names.append(KEYCODE_BUTTON_16, "KEYCODE_BUTTON_16");
+ names.append(KEYCODE_LANGUAGE_SWITCH, "KEYCODE_LANGUAGE_SWITCH");
+ names.append(KEYCODE_MANNER_MODE, "KEYCODE_MANNER_MODE");
+ names.append(KEYCODE_3D_MODE, "KEYCODE_3D_MODE");
};
// Symbolic names of all metakeys in bit order from least significant to most significant.
@@ -1154,7 +1170,18 @@ public class KeyEvent extends InputEvent implements Parcelable {
* @hide
*/
public static final int FLAG_START_TRACKING = 0x40000000;
-
+
+ /**
+ * Private flag that indicates when the system has detected that this key event
+ * may be inconsistent with respect to the sequence of previously delivered key events,
+ * such as when a key up event is sent but the key was not down.
+ *
+ * @hide
+ * @see #isTainted
+ * @see #setTainted
+ */
+ public static final int FLAG_TAINTED = 0x80000000;
+
/**
* Returns the maximum keycode.
*/
@@ -1519,6 +1546,33 @@ public class KeyEvent extends InputEvent implements Parcelable {
}
/**
+ * Obtains a (potentially recycled) copy of another key event.
+ *
+ * @hide
+ */
+ public static KeyEvent obtain(KeyEvent other) {
+ KeyEvent ev = obtain();
+ ev.mDownTime = other.mDownTime;
+ ev.mEventTime = other.mEventTime;
+ ev.mAction = other.mAction;
+ ev.mKeyCode = other.mKeyCode;
+ ev.mRepeatCount = other.mRepeatCount;
+ ev.mMetaState = other.mMetaState;
+ ev.mDeviceId = other.mDeviceId;
+ ev.mScanCode = other.mScanCode;
+ ev.mFlags = other.mFlags;
+ ev.mSource = other.mSource;
+ ev.mCharacters = other.mCharacters;
+ return ev;
+ }
+
+ /** @hide */
+ @Override
+ public KeyEvent copy() {
+ return obtain(this);
+ }
+
+ /**
* Recycles a key event.
* Key events should only be recycled if they are owned by the system since user
* code expects them to be essentially immutable, "tracking" notwithstanding.
@@ -1619,7 +1673,19 @@ public class KeyEvent extends InputEvent implements Parcelable {
event.mFlags = flags;
return event;
}
-
+
+ /** @hide */
+ @Override
+ public final boolean isTainted() {
+ return (mFlags & FLAG_TAINTED) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public final void setTainted(boolean tainted) {
+ mFlags = tainted ? mFlags | FLAG_TAINTED : mFlags & ~FLAG_TAINTED;
+ }
+
/**
* Don't use in new code, instead explicitly check
* {@link #getAction()}.
@@ -2274,6 +2340,12 @@ public class KeyEvent extends InputEvent implements Parcelable {
return mEventTime;
}
+ /** @hide */
+ @Override
+ public final long getEventTimeNano() {
+ return mEventTime * 1000000L;
+ }
+
/**
* Renamed to {@link #getDeviceId}.
*
@@ -2600,15 +2672,22 @@ public class KeyEvent extends InputEvent implements Parcelable {
@Override
public String toString() {
- return "KeyEvent{action=" + actionToString(mAction)
- + " keycode=" + keyCodeToString(mKeyCode)
- + " scancode=" + mScanCode
- + " metaState=" + metaStateToString(mMetaState)
- + " flags=0x" + Integer.toHexString(mFlags)
- + " repeat=" + mRepeatCount
- + " device=" + mDeviceId
- + " source=0x" + Integer.toHexString(mSource)
- + "}";
+ StringBuilder msg = new StringBuilder();
+ msg.append("KeyEvent { action=").append(actionToString(mAction));
+ msg.append(", keyCode=").append(keyCodeToString(mKeyCode));
+ msg.append(", scanCode=").append(mScanCode);
+ if (mCharacters != null) {
+ msg.append(", characters=\"").append(mCharacters).append("\"");
+ }
+ msg.append(", metaState=").append(metaStateToString(mMetaState));
+ msg.append(", flags=0x").append(Integer.toHexString(mFlags));
+ msg.append(", repeatCount=").append(mRepeatCount);
+ msg.append(", eventTime=").append(mEventTime);
+ msg.append(", downTime=").append(mDownTime);
+ msg.append(", deviceId=").append(mDeviceId);
+ msg.append(", source=0x").append(Integer.toHexString(mSource));
+ msg.append(" }");
+ return msg.toString();
}
/**
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 81346b4..332a0fa 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -16,6 +16,10 @@
package android.view;
+import android.graphics.Canvas;
+import android.os.Handler;
+import android.os.Message;
+import android.widget.FrameLayout;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -83,6 +87,7 @@ public abstract class LayoutInflater {
private static final String TAG_MERGE = "merge";
private static final String TAG_INCLUDE = "include";
+ private static final String TAG_1995 = "blink";
private static final String TAG_REQUEST_FOCUS = "requestFocus";
/**
@@ -454,7 +459,12 @@ public abstract class LayoutInflater {
rInflate(parser, root, attrs, false);
} else {
// Temp is the root view that was found in the xml
- View temp = createViewFromTag(root, name, attrs);
+ View temp;
+ if (TAG_1995.equals(name)) {
+ temp = new BlinkLayout(mContext, attrs);
+ } else {
+ temp = createViewFromTag(root, name, attrs);
+ }
ViewGroup.LayoutParams params = null;
@@ -605,10 +615,9 @@ public abstract class LayoutInflater {
* Throw an exception because the specified class is not allowed to be inflated.
*/
private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
- InflateException ie = new InflateException(attrs.getPositionDescription()
+ throw new InflateException(attrs.getPositionDescription()
+ ": Class not allowed to be inflated "
+ (prefix != null ? (prefix + name) : name));
- throw ie;
}
/**
@@ -720,6 +729,12 @@ public abstract class LayoutInflater {
parseInclude(parser, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
+ } else if (TAG_1995.equals(name)) {
+ final View view = new BlinkLayout(mContext, attrs);
+ final ViewGroup viewGroup = (ViewGroup) parent;
+ final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
+ rInflate(parser, view, attrs, true);
+ viewGroup.addView(view, params);
} else {
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
@@ -847,5 +862,64 @@ public abstract class LayoutInflater {
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
// Empty
}
- }
+ }
+
+ private static class BlinkLayout extends FrameLayout {
+ private static final int MESSAGE_BLINK = 0x42;
+ private static final int BLINK_DELAY = 500;
+
+ private boolean mBlink;
+ private boolean mBlinkState;
+ private final Handler mHandler;
+
+ public BlinkLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mHandler = new Handler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MESSAGE_BLINK) {
+ if (mBlink) {
+ mBlinkState = !mBlinkState;
+ makeBlink();
+ }
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private void makeBlink() {
+ Message message = mHandler.obtainMessage(MESSAGE_BLINK);
+ mHandler.sendMessageDelayed(message, BLINK_DELAY);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mBlink = true;
+ mBlinkState = true;
+
+ makeBlink();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ mBlink = false;
+ mBlinkState = true;
+
+ mHandler.removeMessages(MESSAGE_BLINK);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mBlinkState) {
+ super.dispatchDraw(canvas);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 780c52e..dc68264 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -51,6 +51,13 @@ public interface MenuItem {
* it also has an icon specified.
*/
public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
+
+ /**
+ * This item's action view collapses to a normal menu item.
+ * When expanded, the action view temporarily takes over
+ * a larger segment of its container.
+ */
+ public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
/**
* Interface definition for a callback to be invoked when a menu item is
@@ -74,6 +81,34 @@ public interface MenuItem {
}
/**
+ * Interface definition for a callback to be invoked when a menu item
+ * marked with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is
+ * expanded or collapsed.
+ *
+ * @see MenuItem#expandActionView()
+ * @see MenuItem#collapseActionView()
+ * @see MenuItem#setShowAsActionFlags(int)
+ * @see MenuItem#
+ */
+ public interface OnActionExpandListener {
+ /**
+ * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+ * is expanded.
+ * @param item Item that was expanded
+ * @return true if the item should expand, false if expansion should be suppressed.
+ */
+ public boolean onMenuItemActionExpand(MenuItem item);
+
+ /**
+ * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+ * is collapsed.
+ * @param item Item that was collapsed
+ * @return true if the item should collapse, false if collapsing should be suppressed.
+ */
+ public boolean onMenuItemActionCollapse(MenuItem item);
+ }
+
+ /**
* Return the identifier for this menu item. The identifier can not
* be changed after the menu is created.
*
@@ -421,6 +456,27 @@ public interface MenuItem {
public void setShowAsAction(int actionEnum);
/**
+ * Sets how this item should display in the presence of an Action Bar.
+ * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS},
+ * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should
+ * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}.
+ * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action,
+ * it should be shown with a text label.
+ *
+ * <p>Note: This method differs from {@link #setShowAsAction(int)} only in that it
+ * returns the current MenuItem instance for call chaining.
+ *
+ * @param actionEnum How the item should display. One of
+ * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+ * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+ *
+ * @see android.app.ActionBar
+ * @see #setActionView(View)
+ * @return This MenuItem instance for call chaining.
+ */
+ public MenuItem setShowAsActionFlags(int actionEnum);
+
+ /**
* Set an action view for this menu item. An action view will be displayed in place
* of an automatically generated menu item element in the UI when this item is shown
* as an action within a parent.
@@ -453,4 +509,52 @@ public interface MenuItem {
* @see #setShowAsAction(int)
*/
public View getActionView();
+
+ /**
+ * Expand the action view associated with this menu item.
+ * The menu item must have an action view set, as well as
+ * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+ * If a listener has been set using {@link #setOnActionExpandListener(OnActionExpandListener)}
+ * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)}
+ * method invoked. The listener may return false from this method to prevent expanding
+ * the action view.
+ *
+ * @return true if the action view was expanded, false otherwise.
+ */
+ public boolean expandActionView();
+
+ /**
+ * Collapse the action view associated with this menu item.
+ * The menu item must have an action view set, as well as the showAsAction flag
+ * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. If a listener has been set using
+ * {@link #setOnActionExpandListener(OnActionExpandListener)} it will have its
+ * {@link OnActionExpandListener#onMenuItemActionCollapse(MenuItem)} method invoked.
+ * The listener may return false from this method to prevent collapsing the action view.
+ *
+ * @return true if the action view was collapsed, false otherwise.
+ */
+ public boolean collapseActionView();
+
+ /**
+ * Returns true if this menu item's action view has been expanded.
+ *
+ * @return true if the item's action view is expanded, false otherwise.
+ *
+ * @see #expandActionView()
+ * @see #collapseActionView()
+ * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
+ * @see OnActionExpandListener
+ */
+ public boolean isActionViewExpanded();
+
+ /**
+ * Set an {@link OnActionExpandListener} on this menu item to be notified when
+ * the associated action view is expanded or collapsed. The menu item must
+ * be configured to expand or collapse its action view using the flag
+ * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+ *
+ * @param listener Listener that will respond to expand/collapse events
+ * @return This menu item instance for call chaining
+ */
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener);
} \ No newline at end of file
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a17db5d..3436cd1 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -23,53 +23,60 @@ import android.os.SystemClock;
import android.util.SparseArray;
/**
- * Object used to report movement (mouse, pen, finger, trackball) events. This
- * class may hold either absolute or relative movements, depending on what
- * it is being used for.
+ * Object used to report movement (mouse, pen, finger, trackball) events.
+ * Motion events may hold either absolute or relative movements and other data,
+ * depending on the type of device.
+ *
+ * <h3>Overview</h3>
* <p>
- * On pointing devices with source class {@link InputDevice#SOURCE_CLASS_POINTER}
- * such as touch screens, the pointer coordinates specify absolute
- * positions such as view X/Y coordinates. Each complete gesture is represented
- * by a sequence of motion events with actions that describe pointer state transitions
- * and movements. A gesture starts with a motion event with {@link #ACTION_DOWN}
- * that provides the location of the first pointer down. As each additional
- * pointer that goes down or up, the framework will generate a motion event with
- * {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP} accordingly.
- * Pointer movements are described by motion events with {@link #ACTION_MOVE}.
- * Finally, a gesture end either when the final pointer goes up as represented
- * by a motion event with {@link #ACTION_UP} or when gesture is canceled
- * with {@link #ACTION_CANCEL}.
+ * Motion events describe movements in terms of an action code and a set of axis values.
+ * The action code specifies the state change that occurred such as a pointer going
+ * down or up. The axis values describe the position and other movement properties.
* </p><p>
- * Some pointing devices such as mice may support vertical and/or horizontal scrolling.
- * A scroll event is reported as a generic motion event with {@link #ACTION_SCROLL} that
- * includes the relative scroll offset in the {@link #AXIS_VSCROLL} and
- * {@link #AXIS_HSCROLL} axes. See {@link #getAxisValue(int)} for information
- * about retrieving these additional axes.
+ * For example, when the user first touches the screen, the system delivers a touch
+ * event to the appropriate {@link View} with the action code {@link #ACTION_DOWN}
+ * and a set of axis values that include the X and Y coordinates of the touch and
+ * information about the pressure, size and orientation of the contact area.
* </p><p>
- * On trackball devices with source class {@link InputDevice#SOURCE_CLASS_TRACKBALL},
- * the pointer coordinates specify relative movements as X/Y deltas.
- * A trackball gesture consists of a sequence of movements described by motion
- * events with {@link #ACTION_MOVE} interspersed with occasional {@link #ACTION_DOWN}
- * or {@link #ACTION_UP} motion events when the trackball button is pressed or released.
+ * Some devices can report multiple movement traces at the same time. Multi-touch
+ * screens emit one movement trace for each finger. The individual fingers or
+ * other objects that generate movement traces are referred to as <em>pointers</em>.
+ * Motion events contain information about all of the pointers that are currently active
+ * even if some of them have not moved since the last event was delivered.
* </p><p>
- * On joystick devices with source class {@link InputDevice#SOURCE_CLASS_JOYSTICK},
- * the pointer coordinates specify the absolute position of the joystick axes.
- * The joystick axis values are normalized to a range of -1.0 to 1.0 where 0.0 corresponds
- * to the center position. More information about the set of available axes and the
- * range of motion can be obtained using {@link InputDevice#getMotionRange}.
- * Some common joystick axes are {@link #AXIS_X}, {@link #AXIS_Y},
- * {@link #AXIS_HAT_X}, {@link #AXIS_HAT_Y}, {@link #AXIS_Z} and {@link #AXIS_RZ}.
- * </p><p>
- * Motion events always report movements for all pointers at once. The number
- * of pointers only ever changes by one as individual pointers go up and down,
+ * The number of pointers only ever changes by one as individual pointers go up and down,
* except when the gesture is canceled.
* </p><p>
- * The order in which individual pointers appear within a motion event can change
- * from one event to the next. Use the {@link #getPointerId(int)} method to obtain a
- * pointer id to track pointers across motion events in a gesture. Then for
- * successive motion events, use the {@link #findPointerIndex(int)} method to obtain
- * the pointer index for a given pointer id in that motion event.
+ * Each pointer has a unique id that is assigned when it first goes down
+ * (indicated by {@link #ACTION_DOWN} or {@link #ACTION_POINTER_DOWN}). A pointer id
+ * remains valid until the pointer eventually goes up (indicated by {@link #ACTION_UP}
+ * or {@link #ACTION_POINTER_UP}) or when the gesture is canceled (indicated by
+ * {@link #ACTION_CANCEL}).
* </p><p>
+ * The MotionEvent class provides many methods to query the position and other properties of
+ * pointers, such as {@link #getX(int)}, {@link #getY(int)}, {@link #getAxisValue},
+ * {@link #getPointerId(int)}, {@link #getToolType(int)}, and many others. Most of these
+ * methods accept the pointer index as a parameter rather than the pointer id.
+ * The pointer index of each pointer in the event ranges from 0 to one less than the value
+ * returned by {@link #getPointerCount()}.
+ * </p><p>
+ * The order in which individual pointers appear within a motion event is undefined.
+ * Thus the pointer index of a pointer can change from one event to the next but
+ * the pointer id of a pointer is guaranteed to remain constant as long as the pointer
+ * remains active. Use the {@link #getPointerId(int)} method to obtain the
+ * pointer id of a pointer to track it across all subsequent motion events in a gesture.
+ * Then for successive motion events, use the {@link #findPointerIndex(int)} method
+ * to obtain the pointer index for a given pointer id in that motion event.
+ * </p><p>
+ * Mouse and stylus buttons can be retrieved using {@link #getButtonState()}. It is a
+ * good idea to check the button state while handling {@link #ACTION_DOWN} as part
+ * of a touch event. The application may choose to perform some different action
+ * if the touch event starts due to a secondary button click, such as presenting a
+ * context menu.
+ * </p>
+ *
+ * <h3>Batching</h3>
+ * <p>
* For efficiency, motion events with {@link #ACTION_MOVE} may batch together
* multiple movement samples within a single object. The most current
* pointer coordinates are available using {@link #getX(int)} and {@link #getY(int)}.
@@ -98,42 +105,104 @@ import android.util.SparseArray;
* ev.getPointerId(p), ev.getX(p), ev.getY(p));
* }
* }
- * </code></pre></p><p>
- * In general, the framework cannot guarantee that the motion events it delivers
- * to a view always constitute a complete motion sequences since some events may be dropped
- * or modified by containing views before they are delivered. The view implementation
- * should be prepared to handle {@link #ACTION_CANCEL} and should tolerate anomalous
- * situations such as receiving a new {@link #ACTION_DOWN} without first having
- * received an {@link #ACTION_UP} for the prior gesture.
+ * </code></pre></p>
+ *
+ * <h3>Device Types</h3>
+ * <p>
+ * The interpretation of the contents of a MotionEvent varies significantly depending
+ * on the source class of the device.
+ * </p><p>
+ * On pointing devices with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+ * such as touch screens, the pointer coordinates specify absolute
+ * positions such as view X/Y coordinates. Each complete gesture is represented
+ * by a sequence of motion events with actions that describe pointer state transitions
+ * and movements. A gesture starts with a motion event with {@link #ACTION_DOWN}
+ * that provides the location of the first pointer down. As each additional
+ * pointer that goes down or up, the framework will generate a motion event with
+ * {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP} accordingly.
+ * Pointer movements are described by motion events with {@link #ACTION_MOVE}.
+ * Finally, a gesture end either when the final pointer goes up as represented
+ * by a motion event with {@link #ACTION_UP} or when gesture is canceled
+ * with {@link #ACTION_CANCEL}.
+ * </p><p>
+ * Some pointing devices such as mice may support vertical and/or horizontal scrolling.
+ * A scroll event is reported as a generic motion event with {@link #ACTION_SCROLL} that
+ * includes the relative scroll offset in the {@link #AXIS_VSCROLL} and
+ * {@link #AXIS_HSCROLL} axes. See {@link #getAxisValue(int)} for information
+ * about retrieving these additional axes.
+ * </p><p>
+ * On trackball devices with source class {@link InputDevice#SOURCE_CLASS_TRACKBALL},
+ * the pointer coordinates specify relative movements as X/Y deltas.
+ * A trackball gesture consists of a sequence of movements described by motion
+ * events with {@link #ACTION_MOVE} interspersed with occasional {@link #ACTION_DOWN}
+ * or {@link #ACTION_UP} motion events when the trackball button is pressed or released.
+ * </p><p>
+ * On joystick devices with source class {@link InputDevice#SOURCE_CLASS_JOYSTICK},
+ * the pointer coordinates specify the absolute position of the joystick axes.
+ * The joystick axis values are normalized to a range of -1.0 to 1.0 where 0.0 corresponds
+ * to the center position. More information about the set of available axes and the
+ * range of motion can be obtained using {@link InputDevice#getMotionRange}.
+ * Some common joystick axes are {@link #AXIS_X}, {@link #AXIS_Y},
+ * {@link #AXIS_HAT_X}, {@link #AXIS_HAT_Y}, {@link #AXIS_Z} and {@link #AXIS_RZ}.
* </p><p>
* Refer to {@link InputDevice} for more information about how different kinds of
* input devices and sources represent pointer coordinates.
* </p>
+ *
+ * <h3>Consistency Guarantees</h3>
+ * <p>
+ * Motion events are always delivered to views as a consistent stream of events.
+ * What constitutes a consistent stream varies depending on the type of device.
+ * For touch events, consistency implies that pointers go down one at a time,
+ * move around as a group and then go up one at a time or are canceled.
+ * </p><p>
+ * While the framework tries to deliver consistent streams of motion events to
+ * views, it cannot guarantee it. Some events may be dropped or modified by
+ * containing views in the application before they are delivered thereby making
+ * the stream of events inconsistent. Views should always be prepared to
+ * handle {@link #ACTION_CANCEL} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior gesture.
+ * </p>
*/
public final class MotionEvent extends InputEvent implements Parcelable {
private static final long NS_PER_MS = 1000000;
private static final boolean TRACK_RECYCLED_LOCATION = false;
-
+
+ /**
+ * An invalid pointer id.
+ *
+ * This value (-1) can be used as a placeholder to indicate that a pointer id
+ * has not been assigned or is not available. It cannot appear as
+ * a pointer id inside a {@link MotionEvent}.
+ */
+ public static final int INVALID_POINTER_ID = -1;
+
/**
* Bit mask of the parts of the action code that are the action itself.
*/
public static final int ACTION_MASK = 0xff;
/**
- * Constant for {@link #getAction}: A pressed gesture has started, the
+ * Constant for {@link #getActionMasked}: A pressed gesture has started, the
* motion contains the initial starting location.
+ * <p>
+ * This is also a good time to check the button state to distinguish
+ * secondary and tertiary button clicks and handle them appropriately.
+ * Use {@link #getButtonState} to retrieve the button state.
+ * </p>
*/
public static final int ACTION_DOWN = 0;
/**
- * Constant for {@link #getAction}: A pressed gesture has finished, the
+ * Constant for {@link #getActionMasked}: A pressed gesture has finished, the
* motion contains the final release location as well as any intermediate
* points since the last down or move event.
*/
public static final int ACTION_UP = 1;
/**
- * Constant for {@link #getAction}: A change has happened during a
+ * Constant for {@link #getActionMasked}: A change has happened during a
* press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
* The motion contains the most recent point, as well as any intermediate
* points since the last down or move event.
@@ -141,37 +210,49 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int ACTION_MOVE = 2;
/**
- * Constant for {@link #getAction}: The current gesture has been aborted.
+ * Constant for {@link #getActionMasked}: The current gesture has been aborted.
* You will not receive any more points in it. You should treat this as
* an up event, but not perform any action that you normally would.
*/
public static final int ACTION_CANCEL = 3;
/**
- * Constant for {@link #getAction}: A movement has happened outside of the
+ * Constant for {@link #getActionMasked}: A movement has happened outside of the
* normal bounds of the UI element. This does not provide a full gesture,
* but only the initial location of the movement/touch.
*/
public static final int ACTION_OUTSIDE = 4;
/**
- * A non-primary pointer has gone down. The bits in
- * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
+ * Constant for {@link #getActionMasked}: A non-primary pointer has gone down.
+ * <p>
+ * Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
+ * </p><p>
+ * The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
+ * unmasked action returned by {@link #getAction}.
+ * </p>
*/
public static final int ACTION_POINTER_DOWN = 5;
/**
- * A non-primary pointer has gone up. The bits in
- * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
+ * Constant for {@link #getActionMasked}: A non-primary pointer has gone up.
+ * <p>
+ * Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
+ * </p><p>
+ * The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
+ * unmasked action returned by {@link #getAction}.
+ * </p>
*/
public static final int ACTION_POINTER_UP = 6;
/**
- * Constant for {@link #getAction}: A change happened but the pointer
+ * Constant for {@link #getActionMasked}: A change happened but the pointer
* is not down (unlike {@link #ACTION_MOVE}). The motion contains the most
* recent point, as well as any intermediate points since the last
* hover move event.
* <p>
+ * This action is always delivered to the window or view under the pointer.
+ * </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
@@ -180,13 +261,14 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int ACTION_HOVER_MOVE = 7;
/**
- * Constant for {@link #getAction}: The motion event contains relative
+ * Constant for {@link #getActionMasked}: The motion event contains relative
* vertical and/or horizontal scroll offsets. Use {@link #getAxisValue(int)}
* to retrieve the information from {@link #AXIS_VSCROLL} and {@link #AXIS_HSCROLL}.
* The pointer may or may not be down when this event is dispatched.
- * This action is always delivered to the winder under the pointer, which
- * may not be the window currently touched.
* <p>
+ * This action is always delivered to the window or view under the pointer, which
+ * may not be the window or view currently touched.
+ * </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
@@ -195,21 +277,51 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int ACTION_SCROLL = 8;
/**
+ * Constant for {@link #getActionMasked}: The pointer is not down but has entered the
+ * boundaries of a window or view.
+ * <p>
+ * This action is always delivered to the window or view under the pointer.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_HOVER_ENTER = 9;
+
+ /**
+ * Constant for {@link #getActionMasked}: The pointer is not down but has exited the
+ * boundaries of a window or view.
+ * <p>
+ * This action is always delivered to the window or view that was previously under the pointer.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_HOVER_EXIT = 10;
+
+ /**
* Bits in the action code that represent a pointer index, used with
* {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Shifting
* down by {@link #ACTION_POINTER_INDEX_SHIFT} provides the actual pointer
* index where the data for the pointer going up or down can be found; you can
* get its identifier with {@link #getPointerId(int)} and the actual
* data with {@link #getX(int)} etc.
+ *
+ * @see #getActionIndex
*/
public static final int ACTION_POINTER_INDEX_MASK = 0xff00;
/**
* Bit shift for the action bits holding the pointer index as
* defined by {@link #ACTION_POINTER_INDEX_MASK}.
+ *
+ * @see #getActionIndex
*/
public static final int ACTION_POINTER_INDEX_SHIFT = 8;
-
+
/**
* @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
* data index associated with {@link #ACTION_POINTER_DOWN}.
@@ -279,6 +391,17 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
/**
+ * Private flag that indicates when the system has detected that this motion event
+ * may be inconsistent with respect to the sequence of previously delivered motion events,
+ * such as when a pointer move event is sent but the pointer is not down.
+ *
+ * @hide
+ * @see #isTainted
+ * @see #setTainted
+ */
+ public static final int FLAG_TAINTED = 0x80000000;
+
+ /**
* Flag indicating the motion event intersected the top edge of the screen.
*/
public static final int EDGE_TOP = 0x00000001;
@@ -299,7 +422,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int EDGE_RIGHT = 0x00000008;
/**
- * Constant used to identify the X axis of a motion event.
+ * Axis constant: X axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen, reports the absolute X screen position of the center of
@@ -324,7 +447,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_X = 0;
/**
- * Constant used to identify the Y axis of a motion event.
+ * Axis constant: Y axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen, reports the absolute Y screen position of the center of
@@ -349,7 +472,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_Y = 1;
/**
- * Constant used to identify the Pressure axis of a motion event.
+ * Axis constant: Pressure axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen or touch pad, reports the approximate pressure applied to the surface
@@ -371,7 +494,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_PRESSURE = 2;
/**
- * Constant used to identify the Size axis of a motion event.
+ * Axis constant: Size axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen or touch pad, reports the approximate size of the contact area in
@@ -391,7 +514,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_SIZE = 3;
/**
- * Constant used to identify the TouchMajor axis of a motion event.
+ * Axis constant: TouchMajor axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen, reports the length of the major axis of an ellipse that
@@ -412,7 +535,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_TOUCH_MAJOR = 4;
/**
- * Constant used to identify the TouchMinor axis of a motion event.
+ * Axis constant: TouchMinor axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen, reports the length of the minor axis of an ellipse that
@@ -435,7 +558,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_TOUCH_MINOR = 5;
/**
- * Constant used to identify the ToolMajor axis of a motion event.
+ * Axis constant: ToolMajor axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen, reports the length of the major axis of an ellipse that
@@ -460,7 +583,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_TOOL_MAJOR = 6;
/**
- * Constant used to identify the ToolMinor axis of a motion event.
+ * Axis constant: ToolMinor axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen, reports the length of the minor axis of an ellipse that
@@ -485,7 +608,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_TOOL_MINOR = 7;
/**
- * Constant used to identify the Orientation axis of a motion event.
+ * Axis constant: Orientation axis of a motion event.
* <p>
* <ul>
* <li>For a touch screen or touch pad, reports the orientation of the finger
@@ -507,7 +630,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_ORIENTATION = 8;
/**
- * Constant used to identify the Vertical Scroll axis of a motion event.
+ * Axis constant: Vertical Scroll axis of a motion event.
* <p>
* <ul>
* <li>For a mouse, reports the relative movement of the vertical scroll wheel.
@@ -525,7 +648,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_VSCROLL = 9;
/**
- * Constant used to identify the Horizontal Scroll axis of a motion event.
+ * Axis constant: Horizontal Scroll axis of a motion event.
* <p>
* <ul>
* <li>For a mouse, reports the relative movement of the horizontal scroll wheel.
@@ -543,7 +666,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_HSCROLL = 10;
/**
- * Constant used to identify the Z axis of a motion event.
+ * Axis constant: Z axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute Z position of the joystick.
@@ -561,7 +684,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_Z = 11;
/**
- * Constant used to identify the X Rotation axis of a motion event.
+ * Axis constant: X Rotation axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute rotation angle about the X axis.
@@ -577,7 +700,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_RX = 12;
/**
- * Constant used to identify the Y Rotation axis of a motion event.
+ * Axis constant: Y Rotation axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute rotation angle about the Y axis.
@@ -593,7 +716,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_RY = 13;
/**
- * Constant used to identify the Z Rotation axis of a motion event.
+ * Axis constant: Z Rotation axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute rotation angle about the Z axis.
@@ -611,7 +734,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_RZ = 14;
/**
- * Constant used to identify the Hat X axis of a motion event.
+ * Axis constant: Hat X axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute X position of the directional hat control.
@@ -627,7 +750,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_HAT_X = 15;
/**
- * Constant used to identify the Hat Y axis of a motion event.
+ * Axis constant: Hat Y axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute Y position of the directional hat control.
@@ -643,7 +766,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_HAT_Y = 16;
/**
- * Constant used to identify the Left Trigger axis of a motion event.
+ * Axis constant: Left Trigger axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute position of the left trigger control.
@@ -659,7 +782,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_LTRIGGER = 17;
/**
- * Constant used to identify the Right Trigger axis of a motion event.
+ * Axis constant: Right Trigger axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute position of the right trigger control.
@@ -675,7 +798,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_RTRIGGER = 18;
/**
- * Constant used to identify the Throttle axis of a motion event.
+ * Axis constant: Throttle axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute position of the throttle control.
@@ -691,7 +814,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_THROTTLE = 19;
/**
- * Constant used to identify the Rudder axis of a motion event.
+ * Axis constant: Rudder axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute position of the rudder control.
@@ -707,7 +830,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_RUDDER = 20;
/**
- * Constant used to identify the Wheel axis of a motion event.
+ * Axis constant: Wheel axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute position of the steering wheel control.
@@ -723,7 +846,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_WHEEL = 21;
/**
- * Constant used to identify the Gas axis of a motion event.
+ * Axis constant: Gas axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute position of the gas (accelerator) control.
@@ -740,7 +863,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GAS = 22;
/**
- * Constant used to identify the Brake axis of a motion event.
+ * Axis constant: Brake axis of a motion event.
* <p>
* <ul>
* <li>For a joystick, reports the absolute position of the brake control.
@@ -756,7 +879,24 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_BRAKE = 23;
/**
- * Constant used to identify the Generic 1 axis of a motion event.
+ * Axis constant: Distance axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a stylus, reports the distance of the stylus from the screen.
+ * The value is nominally measured in millimeters where 0.0 indicates direct contact
+ * and larger values indicate increasing distance from the surface.
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_DISTANCE = 24;
+
+ /**
+ * Axis constant: Generic 1 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -767,7 +907,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_1 = 32;
/**
- * Constant used to identify the Generic 2 axis of a motion event.
+ * Axis constant: Generic 2 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -778,7 +918,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_2 = 33;
/**
- * Constant used to identify the Generic 3 axis of a motion event.
+ * Axis constant: Generic 3 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -789,7 +929,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_3 = 34;
/**
- * Constant used to identify the Generic 4 axis of a motion event.
+ * Axis constant: Generic 4 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -800,7 +940,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_4 = 35;
/**
- * Constant used to identify the Generic 5 axis of a motion event.
+ * Axis constant: Generic 5 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -811,7 +951,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_5 = 36;
/**
- * Constant used to identify the Generic 6 axis of a motion event.
+ * Axis constant: Generic 6 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -822,7 +962,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_6 = 37;
/**
- * Constant used to identify the Generic 7 axis of a motion event.
+ * Axis constant: Generic 7 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -833,7 +973,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_7 = 38;
/**
- * Constant used to identify the Generic 8 axis of a motion event.
+ * Axis constant: Generic 8 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -844,7 +984,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_8 = 39;
/**
- * Constant used to identify the Generic 9 axis of a motion event.
+ * Axis constant: Generic 9 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -855,7 +995,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_9 = 40;
/**
- * Constant used to identify the Generic 10 axis of a motion event.
+ * Axis constant: Generic 10 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -866,7 +1006,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_10 = 41;
/**
- * Constant used to identify the Generic 11 axis of a motion event.
+ * Axis constant: Generic 11 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -877,7 +1017,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_11 = 42;
/**
- * Constant used to identify the Generic 12 axis of a motion event.
+ * Axis constant: Generic 12 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -888,7 +1028,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_12 = 43;
/**
- * Constant used to identify the Generic 13 axis of a motion event.
+ * Axis constant: Generic 13 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -899,7 +1039,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_13 = 44;
/**
- * Constant used to identify the Generic 14 axis of a motion event.
+ * Axis constant: Generic 14 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -910,7 +1050,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_14 = 45;
/**
- * Constant used to identify the Generic 15 axis of a motion event.
+ * Axis constant: Generic 15 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -921,7 +1061,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int AXIS_GENERIC_15 = 46;
/**
- * Constant used to identify the Generic 16 axis of a motion event.
+ * Axis constant: Generic 16 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*
* @see #getAxisValue(int, int)
@@ -937,7 +1077,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
// Symbolic names of all axes.
private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
- private static void populateAxisSymbolicNames() {
+ static {
SparseArray<String> names = AXIS_SYMBOLIC_NAMES;
names.append(AXIS_X, "AXIS_X");
names.append(AXIS_Y, "AXIS_Y");
@@ -963,6 +1103,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
names.append(AXIS_WHEEL, "AXIS_WHEEL");
names.append(AXIS_GAS, "AXIS_GAS");
names.append(AXIS_BRAKE, "AXIS_BRAKE");
+ names.append(AXIS_DISTANCE, "AXIS_DISTANCE");
names.append(AXIS_GENERIC_1, "AXIS_GENERIC_1");
names.append(AXIS_GENERIC_2, "AXIS_GENERIC_2");
names.append(AXIS_GENERIC_3, "AXIS_GENERIC_3");
@@ -981,8 +1122,169 @@ public final class MotionEvent extends InputEvent implements Parcelable {
names.append(AXIS_GENERIC_16, "AXIS_GENERIC_16");
}
+ /**
+ * Button constant: Primary button (left mouse button, stylus tip).
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_PRIMARY = 1 << 0;
+
+ /**
+ * Button constant: Secondary button (right mouse button, stylus barrel).
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_SECONDARY = 1 << 1;
+
+ /**
+ * Button constant: Tertiary button (middle mouse button).
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_TERTIARY = 1 << 2;
+
+ /**
+ * Button constant: Back button pressed (mouse back button).
+ * <p>
+ * The system may send a {@link KeyEvent#KEYCODE_BACK} key press to the application
+ * when this button is pressed.
+ * </p>
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_BACK = 1 << 3;
+
+ /**
+ * Button constant: Forward button pressed (mouse forward button).
+ * <p>
+ * The system may send a {@link KeyEvent#KEYCODE_FORWARD} key press to the application
+ * when this button is pressed.
+ * </p>
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_FORWARD = 1 << 4;
+
+ /**
+ * Button constant: Eraser button pressed (stylus end).
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_ERASER = 1 << 5;
+
+ // NOTE: If you add a new axis here you must also add it to:
+ // native/include/android/input.h
+
+ // Symbolic names of all button states in bit order from least significant
+ // to most significant.
+ private static final String[] BUTTON_SYMBOLIC_NAMES = new String[] {
+ "BUTTON_PRIMARY",
+ "BUTTON_SECONDARY",
+ "BUTTON_TERTIARY",
+ "BUTTON_BACK",
+ "BUTTON_FORWARD",
+ "BUTTON_ERASER",
+ "0x00000040",
+ "0x00000080",
+ "0x00000100",
+ "0x00000200",
+ "0x00000400",
+ "0x00000800",
+ "0x00001000",
+ "0x00002000",
+ "0x00004000",
+ "0x00008000",
+ "0x00010000",
+ "0x00020000",
+ "0x00040000",
+ "0x00080000",
+ "0x00100000",
+ "0x00200000",
+ "0x00400000",
+ "0x00800000",
+ "0x01000000",
+ "0x02000000",
+ "0x04000000",
+ "0x08000000",
+ "0x10000000",
+ "0x20000000",
+ "0x40000000",
+ "0x80000000",
+ };
+
+ /**
+ * Tool type constant: Unknown tool type.
+ * This constant is used when the tool type is not known or is not relevant,
+ * such as for a trackball or other non-pointing device.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_UNKNOWN = 0;
+
+ /**
+ * Tool type constant: The tool is a finger directly touching the display.
+ *
+ * This is a <em>direct</em> positioning tool.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_FINGER = 1;
+
+ /**
+ * Tool type constant: The tool is a stylus directly touching the display
+ * or hovering slightly above it.
+ *
+ * This is a <em>direct</em> positioning tool.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_STYLUS = 2;
+
+ /**
+ * Tool type constant: The tool is a mouse or trackpad that translates
+ * relative motions into cursor movements on the display.
+ *
+ * This is an <em>indirect</em> positioning tool.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_MOUSE = 3;
+
+ /**
+ * Tool type constant: The tool is a finger on a touch pad that is not
+ * directly attached to the display. Finger movements on the touch pad
+ * may be translated into touches on the display, possibly with visual feedback.
+ *
+ * This is an <em>indirect</em> positioning tool.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_INDIRECT_FINGER = 4;
+
+ /**
+ * Tool type constant: The tool is a stylus on a digitizer tablet that is not
+ * attached to the display. Stylus movements on the digitizer may be translated
+ * into touches on the display, possibly with visual feedback.
+ *
+ * This is an <em>indirect</em> positioning tool.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_INDIRECT_STYLUS = 5;
+
+ // NOTE: If you add a new tool type here you must also add it to:
+ // native/include/android/input.h
+
+ // Symbolic names of all tool types.
+ private static final SparseArray<String> TOOL_TYPE_SYMBOLIC_NAMES = new SparseArray<String>();
static {
- populateAxisSymbolicNames();
+ SparseArray<String> names = TOOL_TYPE_SYMBOLIC_NAMES;
+ names.append(TOOL_TYPE_UNKNOWN, "TOOL_TYPE_UNKNOWN");
+ names.append(TOOL_TYPE_FINGER, "TOOL_TYPE_FINGER");
+ names.append(TOOL_TYPE_STYLUS, "TOOL_TYPE_STYLUS");
+ names.append(TOOL_TYPE_MOUSE, "TOOL_TYPE_MOUSE");
+ names.append(TOOL_TYPE_INDIRECT_FINGER, "TOOL_TYPE_INDIRECT_FINGER");
+ names.append(TOOL_TYPE_INDIRECT_STYLUS, "TOOL_TYPE_INDIRECT_STYLUS");
}
// Private value for history pos that obtains the current sample.
@@ -995,10 +1297,23 @@ public final class MotionEvent extends InputEvent implements Parcelable {
// Shared temporary objects used when translating coordinates supplied by
// the caller into single element PointerCoords and pointer id arrays.
- // Must lock gTmpPointerCoords prior to use.
- private static final PointerCoords[] gTmpPointerCoords =
- new PointerCoords[] { new PointerCoords() };
- private static final int[] gTmpPointerIds = new int[] { 0 /*always 0*/ };
+ private static final Object gSharedTempLock = new Object();
+ private static PointerCoords[] gSharedTempPointerCoords;
+ private static PointerProperties[] gSharedTempPointerProperties;
+ private static int[] gSharedTempPointerIndexMap;
+
+ private static final void ensureSharedTempPointerCapacity(int desiredCapacity) {
+ if (gSharedTempPointerCoords == null
+ || gSharedTempPointerCoords.length < desiredCapacity) {
+ int capacity = gSharedTempPointerCoords != null ? gSharedTempPointerCoords.length : 8;
+ while (capacity < desiredCapacity) {
+ capacity *= 2;
+ }
+ gSharedTempPointerCoords = PointerCoords.createArray(capacity);
+ gSharedTempPointerProperties = PointerProperties.createArray(capacity);
+ gSharedTempPointerIndexMap = new int[capacity];
+ }
+ }
// Pointer to the native MotionEvent object that contains the actual data.
private int mNativePtr;
@@ -1008,10 +1323,11 @@ public final class MotionEvent extends InputEvent implements Parcelable {
private boolean mRecycled;
private static native int nativeInitialize(int nativePtr,
- int deviceId, int source, int action, int flags, int edgeFlags, int metaState,
+ int deviceId, int source, int action, int flags, int edgeFlags,
+ int metaState, int buttonState,
float xOffset, float yOffset, float xPrecision, float yPrecision,
long downTimeNanos, long eventTimeNanos,
- int pointerCount, int[] pointerIds, PointerCoords[] pointerCoords);
+ int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords);
private static native int nativeCopy(int destNativePtr, int sourceNativePtr,
boolean keepHistory);
private static native void nativeDispose(int nativePtr);
@@ -1025,16 +1341,22 @@ public final class MotionEvent extends InputEvent implements Parcelable {
private static native void nativeSetAction(int nativePtr, int action);
private static native boolean nativeIsTouchEvent(int nativePtr);
private static native int nativeGetFlags(int nativePtr);
+ private static native void nativeSetFlags(int nativePtr, int flags);
private static native int nativeGetEdgeFlags(int nativePtr);
private static native void nativeSetEdgeFlags(int nativePtr, int action);
private static native int nativeGetMetaState(int nativePtr);
+ private static native int nativeGetButtonState(int nativePtr);
private static native void nativeOffsetLocation(int nativePtr, float deltaX, float deltaY);
+ private static native float nativeGetXOffset(int nativePtr);
+ private static native float nativeGetYOffset(int nativePtr);
private static native float nativeGetXPrecision(int nativePtr);
private static native float nativeGetYPrecision(int nativePtr);
private static native long nativeGetDownTimeNanos(int nativePtr);
+ private static native void nativeSetDownTimeNanos(int nativePtr, long downTime);
private static native int nativeGetPointerCount(int nativePtr);
private static native int nativeGetPointerId(int nativePtr, int pointerIndex);
+ private static native int nativeGetToolType(int nativePtr, int pointerIndex);
private static native int nativeFindPointerIndex(int nativePtr, int pointerId);
private static native int nativeGetHistorySize(int nativePtr);
@@ -1045,6 +1367,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
int axis, int pointerIndex, int historyPos);
private static native void nativeGetPointerCoords(int nativePtr,
int pointerIndex, int historyPos, PointerCoords outPointerCoords);
+ private static native void nativeGetPointerProperties(int nativePtr,
+ int pointerIndex, PointerProperties outPointerProperties);
private static native void nativeScale(int nativePtr, float scale);
private static native void nativeTransform(int nativePtr, Matrix matrix);
@@ -1086,19 +1410,21 @@ public final class MotionEvent extends InputEvent implements Parcelable {
/**
* Create a new MotionEvent, filling in all of the basic values that
* define the motion.
- *
+ *
* @param downTime The time (in ms) when the user originally pressed down to start
* a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
* @param eventTime The the time (in ms) when this specific event was generated. This
* must be obtained from {@link SystemClock#uptimeMillis()}.
* @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
- * @param pointers The number of points that will be in this event.
- * @param pointerIds An array of <em>pointers</em> values providing
- * an identifier for each pointer.
- * @param pointerCoords An array of <em>pointers</em> values providing
+ * @param pointerCount The number of pointers that will be in this event.
+ * @param pointerProperties An array of <em>pointerCount</em> values providing
+ * a {@link PointerProperties} property object for each pointer, which must
+ * include the pointer identifier.
+ * @param pointerCoords An array of <em>pointerCount</em> values providing
* a {@link PointerCoords} coordinate object for each pointer.
* @param metaState The state of any meta / modifier keys that were in effect when
* the event was generated.
+ * @param buttonState The state of buttons that are pressed.
* @param xPrecision The precision of the X coordinate being reported.
* @param yPrecision The precision of the Y coordinate being reported.
* @param deviceId The id for the device that this event came from. An id of
@@ -1110,21 +1436,69 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* @param flags The motion event flags.
*/
static public MotionEvent obtain(long downTime, long eventTime,
- int action, int pointers, int[] pointerIds, PointerCoords[] pointerCoords,
- int metaState, float xPrecision, float yPrecision, int deviceId,
+ int action, int pointerCount, PointerProperties[] pointerProperties,
+ PointerCoords[] pointerCoords, int metaState, int buttonState,
+ float xPrecision, float yPrecision, int deviceId,
int edgeFlags, int source, int flags) {
MotionEvent ev = obtain();
ev.mNativePtr = nativeInitialize(ev.mNativePtr,
- deviceId, source, action, flags, edgeFlags, metaState,
+ deviceId, source, action, flags, edgeFlags, metaState, buttonState,
0, 0, xPrecision, yPrecision,
downTime * NS_PER_MS, eventTime * NS_PER_MS,
- pointers, pointerIds, pointerCoords);
+ pointerCount, pointerProperties, pointerCoords);
return ev;
}
/**
* Create a new MotionEvent, filling in all of the basic values that
* define the motion.
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+ * @param pointerCount The number of pointers that will be in this event.
+ * @param pointerIds An array of <em>pointerCount</em> values providing
+ * an identifier for each pointer.
+ * @param pointerCoords An array of <em>pointerCount</em> values providing
+ * a {@link PointerCoords} coordinate object for each pointer.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ * @param xPrecision The precision of the X coordinate being reported.
+ * @param yPrecision The precision of the Y coordinate being reported.
+ * @param deviceId The id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device; other
+ * numbers are arbitrary and you shouldn't depend on the values.
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+ * MotionEvent.
+ * @param source The source of this event.
+ * @param flags The motion event flags.
+ *
+ * @deprecated Use {@link #obtain(long, long, int, int, PointerProperties[], PointerCoords[], int, int, float, float, int, int, int, int)}
+ * instead.
+ */
+ @Deprecated
+ static public MotionEvent obtain(long downTime, long eventTime,
+ int action, int pointerCount, int[] pointerIds, PointerCoords[] pointerCoords,
+ int metaState, float xPrecision, float yPrecision, int deviceId,
+ int edgeFlags, int source, int flags) {
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(pointerCount);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ for (int i = 0; i < pointerCount; i++) {
+ pp[i].clear();
+ pp[i].id = pointerIds[i];
+ }
+ return obtain(downTime, eventTime, action, pointerCount, pp,
+ pointerCoords, metaState, 0, xPrecision, yPrecision, deviceId,
+ edgeFlags, source, flags);
+ }
+ }
+
+ /**
+ * Create a new MotionEvent, filling in all of the basic values that
+ * define the motion.
*
* @param downTime The time (in ms) when the user originally pressed down to start
* a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
@@ -1154,20 +1528,25 @@ public final class MotionEvent extends InputEvent implements Parcelable {
static public MotionEvent obtain(long downTime, long eventTime, int action,
float x, float y, float pressure, float size, int metaState,
float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
- synchronized (gTmpPointerCoords) {
- final PointerCoords pc = gTmpPointerCoords[0];
- pc.clear();
- pc.x = x;
- pc.y = y;
- pc.pressure = pressure;
- pc.size = size;
-
- MotionEvent ev = obtain();
+ MotionEvent ev = obtain();
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(1);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ pp[0].clear();
+ pp[0].id = 0;
+
+ final PointerCoords pc[] = gSharedTempPointerCoords;
+ pc[0].clear();
+ pc[0].x = x;
+ pc[0].y = y;
+ pc[0].pressure = pressure;
+ pc[0].size = size;
+
ev.mNativePtr = nativeInitialize(ev.mNativePtr,
- deviceId, InputDevice.SOURCE_UNKNOWN, action, 0, edgeFlags, metaState,
+ deviceId, InputDevice.SOURCE_UNKNOWN, action, 0, edgeFlags, metaState, 0,
0, 0, xPrecision, yPrecision,
downTime * NS_PER_MS, eventTime * NS_PER_MS,
- 1, gTmpPointerIds, gTmpPointerCoords);
+ 1, pp, pc);
return ev;
}
}
@@ -1181,7 +1560,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* @param eventTime The the time (in ms) when this specific event was generated. This
* must be obtained from {@link SystemClock#uptimeMillis()}.
* @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
- * @param pointers The number of pointers that are active in this event.
+ * @param pointerCount The number of pointers that are active in this event.
* @param x The X coordinate of this event.
* @param y The Y coordinate of this event.
* @param pressure The current pressure of this event. The pressure generally
@@ -1207,7 +1586,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*/
@Deprecated
static public MotionEvent obtain(long downTime, long eventTime, int action,
- int pointers, float x, float y, float pressure, float size, int metaState,
+ int pointerCount, float x, float y, float pressure, float size, int metaState,
float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
return obtain(downTime, eventTime, action, x, y, pressure, size,
metaState, xPrecision, yPrecision, deviceId, edgeFlags);
@@ -1261,6 +1640,12 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return ev;
}
+ /** @hide */
+ @Override
+ public MotionEvent copy() {
+ return obtain(this);
+ }
+
/**
* Recycle the MotionEvent, to be re-used by a later caller. After calling
* this function you must not ever touch the event again.
@@ -1354,9 +1739,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
/**
* Returns true if this motion event is a touch event.
* <p>
- * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE}
- * or {@link #ACTION_SCROLL} because they are not actually touch events
- * (the pointer is not down).
+ * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE},
+ * {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_EXIT}, or {@link #ACTION_SCROLL}
+ * because they are not actually touch events (the pointer is not down).
* </p>
* @return True if this motion event is a touch event.
* @hide
@@ -1374,6 +1759,20 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return nativeGetFlags(mNativePtr);
}
+ /** @hide */
+ @Override
+ public final boolean isTainted() {
+ final int flags = getFlags();
+ return (flags & FLAG_TAINTED) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public final void setTainted(boolean tainted) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
+ }
+
/**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
@@ -1383,6 +1782,16 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
/**
+ * Sets the time (in ms) when the user originally pressed down to start
+ * a stream of position events.
+ *
+ * @hide
+ */
+ public final void setDownTime(long downTime) {
+ nativeSetDownTimeNanos(mNativePtr, downTime * NS_PER_MS);
+ }
+
+ /**
* Returns the time (in ms) when this specific event was generated.
*/
public final long getEventTime() {
@@ -1521,7 +1930,27 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public final int getPointerId(int pointerIndex) {
return nativeGetPointerId(mNativePtr, pointerIndex);
}
-
+
+ /**
+ * Gets the tool type of a pointer for the given pointer index.
+ * The tool type indicates the type of tool used to make contact such
+ * as a finger or stylus, if known.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @return The tool type of the pointer.
+ *
+ * @see #TOOL_TYPE_UNKNOWN
+ * @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);
+ }
+
/**
* Given a pointer identifier, find the index of its data in the event.
*
@@ -1533,7 +1962,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public final int findPointerIndex(int pointerId) {
return nativeFindPointerIndex(mNativePtr, pointerId);
}
-
+
/**
* Returns the X coordinate of this event for the given pointer
* <em>index</em> (use {@link #getPointerId(int)} to find the pointer
@@ -1709,6 +2138,21 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
/**
+ * Populates a {@link PointerProperties} object with pointer properties for
+ * the specified pointer index.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param outPointerProperties The pointer properties object to populate.
+ *
+ * @see PointerProperties
+ */
+ public final void getPointerProperties(int pointerIndex,
+ PointerProperties outPointerProperties) {
+ nativeGetPointerProperties(mNativePtr, pointerIndex, outPointerProperties);
+ }
+
+ /**
* Returns the state of any meta / modifier keys that were in effect when
* the event was generated. This is the same values as those
* returned by {@link KeyEvent#getMetaState() KeyEvent.getMetaState}.
@@ -1723,6 +2167,22 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
/**
+ * Gets the state of all buttons that are pressed such as a mouse or stylus button.
+ *
+ * @return The button state.
+ *
+ * @see #BUTTON_PRIMARY
+ * @see #BUTTON_SECONDARY
+ * @see #BUTTON_TERTIARY
+ * @see #BUTTON_FORWARD
+ * @see #BUTTON_BACK
+ * @see #BUTTON_ERASER
+ */
+ public final int getButtonState() {
+ return nativeGetButtonState(mNativePtr);
+ }
+
+ /**
* Returns the original raw X coordinate of this event. For touch
* events on the screen, this is the original location of the event
* on the screen, before it had been adjusted for the containing window
@@ -2236,14 +2696,16 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*/
public final void addBatch(long eventTime, float x, float y,
float pressure, float size, int metaState) {
- synchronized (gTmpPointerCoords) {
- final PointerCoords pc = gTmpPointerCoords[0];
- pc.clear();
- pc.x = x;
- pc.y = y;
- pc.pressure = pressure;
- pc.size = size;
- nativeAddBatch(mNativePtr, eventTime * NS_PER_MS, gTmpPointerCoords, metaState);
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(1);
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+ pc[0].clear();
+ pc[0].x = x;
+ pc[0].y = y;
+ pc[0].pressure = pressure;
+ pc[0].size = size;
+
+ nativeAddBatch(mNativePtr, eventTime * NS_PER_MS, pc, metaState);
}
}
@@ -2262,30 +2724,187 @@ public final class MotionEvent extends InputEvent implements Parcelable {
nativeAddBatch(mNativePtr, eventTime * NS_PER_MS, pointerCoords, metaState);
}
+ /**
+ * Returns true if all points in the motion event are completely within the specified bounds.
+ * @hide
+ */
+ public final boolean isWithinBoundsNoHistory(float left, float top,
+ float right, float bottom) {
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+ for (int i = 0; i < pointerCount; i++) {
+ final float x = nativeGetAxisValue(mNativePtr, AXIS_X, i, HISTORY_CURRENT);
+ final float y = nativeGetAxisValue(mNativePtr, AXIS_Y, i, HISTORY_CURRENT);
+ if (x < left || x > right || y < top || y > bottom) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static final float clamp(float value, float low, float high) {
+ if (value < low) {
+ return low;
+ } else if (value > high) {
+ return high;
+ }
+ return value;
+ }
+
+ /**
+ * Returns a new motion events whose points have been clamped to the specified bounds.
+ * @hide
+ */
+ public final MotionEvent clampNoHistory(float left, float top, float right, float bottom) {
+ MotionEvent ev = obtain();
+ synchronized (gSharedTempLock) {
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+
+ ensureSharedTempPointerCapacity(pointerCount);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerProperties(mNativePtr, i, pp[i]);
+ nativeGetPointerCoords(mNativePtr, i, HISTORY_CURRENT, pc[i]);
+ pc[i].x = clamp(pc[i].x, left, right);
+ pc[i].y = clamp(pc[i].y, top, bottom);
+ }
+ ev.mNativePtr = nativeInitialize(ev.mNativePtr,
+ nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
+ nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
+ nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
+ nativeGetButtonState(mNativePtr),
+ nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+ nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
+ nativeGetDownTimeNanos(mNativePtr),
+ nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
+ pointerCount, pp, pc);
+ return ev;
+ }
+ }
+
+ /**
+ * Gets an integer where each pointer id present in the event is marked as a bit.
+ * @hide
+ */
+ public final int getPointerIdBits() {
+ int idBits = 0;
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+ for (int i = 0; i < pointerCount; i++) {
+ idBits |= 1 << nativeGetPointerId(mNativePtr, i);
+ }
+ return idBits;
+ }
+
+ /**
+ * Splits a motion event such that it includes only a subset of pointer ids.
+ * @hide
+ */
+ public final MotionEvent split(int idBits) {
+ MotionEvent ev = obtain();
+ synchronized (gSharedTempLock) {
+ final int oldPointerCount = nativeGetPointerCount(mNativePtr);
+ ensureSharedTempPointerCapacity(oldPointerCount);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+ final int[] map = gSharedTempPointerIndexMap;
+
+ final int oldAction = nativeGetAction(mNativePtr);
+ final int oldActionMasked = oldAction & ACTION_MASK;
+ final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
+ >> ACTION_POINTER_INDEX_SHIFT;
+ int newActionPointerIndex = -1;
+ int newPointerCount = 0;
+ int newIdBits = 0;
+ for (int i = 0; i < oldPointerCount; i++) {
+ nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
+ final int idBit = 1 << pp[newPointerCount].id;
+ if ((idBit & idBits) != 0) {
+ if (i == oldActionPointerIndex) {
+ newActionPointerIndex = newPointerCount;
+ }
+ map[newPointerCount] = i;
+ newPointerCount += 1;
+ newIdBits |= idBit;
+ }
+ }
+
+ if (newPointerCount == 0) {
+ throw new IllegalArgumentException("idBits did not match any ids in the event");
+ }
+
+ final int newAction;
+ if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
+ if (newActionPointerIndex < 0) {
+ // An unrelated pointer changed.
+ newAction = ACTION_MOVE;
+ } else if (newPointerCount == 1) {
+ // The first/last pointer went down/up.
+ newAction = oldActionMasked == ACTION_POINTER_DOWN
+ ? ACTION_DOWN : ACTION_UP;
+ } else {
+ // A secondary pointer went down/up.
+ newAction = oldActionMasked
+ | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
+ }
+ } else {
+ // Simple up/down/cancel/move or other motion action.
+ newAction = oldAction;
+ }
+
+ final int historySize = nativeGetHistorySize(mNativePtr);
+ for (int h = 0; h <= historySize; h++) {
+ final int historyPos = h == historySize ? HISTORY_CURRENT : h;
+
+ for (int i = 0; i < newPointerCount; i++) {
+ nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
+ }
+
+ final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
+ if (h == 0) {
+ ev.mNativePtr = nativeInitialize(ev.mNativePtr,
+ nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
+ newAction, nativeGetFlags(mNativePtr),
+ nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
+ nativeGetButtonState(mNativePtr),
+ nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+ nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
+ nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
+ newPointerCount, pp, pc);
+ } else {
+ nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
+ }
+ }
+ return ev;
+ }
+ }
+
@Override
public String toString() {
- return "MotionEvent{" + Integer.toHexString(System.identityHashCode(this))
- + " pointerId=" + getPointerId(0)
- + " action=" + actionToString(getAction())
- + " x=" + getX()
- + " y=" + getY()
- + " pressure=" + getPressure()
- + " size=" + getSize()
- + " touchMajor=" + getTouchMajor()
- + " touchMinor=" + getTouchMinor()
- + " toolMajor=" + getToolMajor()
- + " toolMinor=" + getToolMinor()
- + " orientation=" + getOrientation()
- + " meta=" + KeyEvent.metaStateToString(getMetaState())
- + " pointerCount=" + getPointerCount()
- + " historySize=" + getHistorySize()
- + " flags=0x" + Integer.toHexString(getFlags())
- + " edgeFlags=0x" + Integer.toHexString(getEdgeFlags())
- + " device=" + getDeviceId()
- + " source=0x" + Integer.toHexString(getSource())
- + (getPointerCount() > 1 ?
- " pointerId2=" + getPointerId(1) + " x2=" + getX(1) + " y2=" + getY(1) : "")
- + "}";
+ StringBuilder msg = new StringBuilder();
+ msg.append("MotionEvent { action=").append(actionToString(getAction()));
+
+ final int pointerCount = getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ msg.append(", id[").append(i).append("]=").append(getPointerId(i));
+ msg.append(", x[").append(i).append("]=").append(getX(i));
+ msg.append(", y[").append(i).append("]=").append(getY(i));
+ msg.append(", toolType[").append(i).append("]=").append(
+ toolTypeToString(getToolType(i)));
+ }
+
+ msg.append(", buttonState=").append(KeyEvent.metaStateToString(getButtonState()));
+ msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState()));
+ msg.append(", flags=0x").append(Integer.toHexString(getFlags()));
+ msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags()));
+ msg.append(", pointerCount=").append(pointerCount);
+ msg.append(", historySize=").append(getHistorySize());
+ msg.append(", eventTime=").append(getEventTime());
+ msg.append(", downTime=").append(getDownTime());
+ msg.append(", deviceId=").append(getDeviceId());
+ msg.append(", source=0x").append(Integer.toHexString(getSource()));
+ msg.append(" }");
+ return msg.toString();
}
/**
@@ -2313,6 +2932,10 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return "ACTION_HOVER_MOVE";
case ACTION_SCROLL:
return "ACTION_SCROLL";
+ case ACTION_HOVER_ENTER:
+ return "ACTION_HOVER_ENTER";
+ case ACTION_HOVER_EXIT:
+ return "ACTION_HOVER_EXIT";
}
int index = (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
switch (action & ACTION_MASK) {
@@ -2364,6 +2987,55 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
}
+ /**
+ * Returns a string that represents the symbolic name of the specified combined
+ * button state flags such as "0", "BUTTON_PRIMARY",
+ * "BUTTON_PRIMARY|BUTTON_SECONDARY" or an equivalent numeric constant such as "0x10000000"
+ * if unknown.
+ *
+ * @param buttonState The button state.
+ * @return The symbolic name of the specified combined button state flags.
+ * @hide
+ */
+ public static String buttonStateToString(int buttonState) {
+ if (buttonState == 0) {
+ return "0";
+ }
+ StringBuilder result = null;
+ int i = 0;
+ while (buttonState != 0) {
+ final boolean isSet = (buttonState & 1) != 0;
+ buttonState >>>= 1; // unsigned shift!
+ if (isSet) {
+ final String name = BUTTON_SYMBOLIC_NAMES[i];
+ if (result == null) {
+ if (buttonState == 0) {
+ return name;
+ }
+ result = new StringBuilder(name);
+ } else {
+ result.append('|');
+ result.append(name);
+ }
+ }
+ i += 1;
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified tool type
+ * such as "TOOL_TYPE_FINGER" or an equivalent numeric constant such as "42" if unknown.
+ *
+ * @param toolType The tool type.
+ * @return The symbolic name of the specified tool type.
+ * @hide
+ */
+ public static String toolTypeToString(int toolType) {
+ String symbolicName = TOOL_TYPE_SYMBOLIC_NAMES.get(toolType);
+ return symbolicName != null ? symbolicName : Integer.toString(toolType);
+ }
+
public static final Parcelable.Creator<MotionEvent> CREATOR
= new Parcelable.Creator<MotionEvent>() {
public MotionEvent createFromParcel(Parcel in) {
@@ -2391,8 +3063,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
/**
* Transfer object for pointer coordinates.
*
- * Objects of this type can be used to manufacture new {@link MotionEvent} objects
- * and to query pointer coordinate information in bulk.
+ * Objects of this type can be used to specify the pointer coordinates when
+ * creating new {@link MotionEvent} objects and to query pointer coordinates
+ * in bulk.
*
* Refer to {@link InputDevice} for information about how different kinds of
* input devices and sources represent pointer coordinates.
@@ -2418,6 +3091,15 @@ public final class MotionEvent extends InputEvent implements Parcelable {
copyFrom(other);
}
+ /** @hide */
+ public static PointerCoords[] createArray(int size) {
+ PointerCoords[] array = new PointerCoords[size];
+ for (int i = 0; i < size; i++) {
+ array[i] = new PointerCoords();
+ }
+ return array;
+ }
+
/**
* The X component of the pointer movement.
*
@@ -2678,4 +3360,71 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
}
}
+
+ /**
+ * Transfer object for pointer properties.
+ *
+ * Objects of this type can be used to specify the pointer id and tool type
+ * when creating new {@link MotionEvent} objects and to query pointer properties in bulk.
+ */
+ public static final class PointerProperties {
+ /**
+ * Creates a pointer properties object with an invalid pointer id.
+ */
+ public PointerProperties() {
+ clear();
+ }
+
+ /**
+ * Creates a pointer properties object as a copy of the contents of
+ * another pointer properties object.
+ * @param other
+ */
+ public PointerProperties(PointerProperties other) {
+ copyFrom(other);
+ }
+
+ /** @hide */
+ public static PointerProperties[] createArray(int size) {
+ PointerProperties[] array = new PointerProperties[size];
+ for (int i = 0; i < size; i++) {
+ array[i] = new PointerProperties();
+ }
+ return array;
+ }
+
+ /**
+ * The pointer id.
+ * Initially set to {@link #INVALID_POINTER_ID} (-1).
+ *
+ * @see MotionEvent#getPointerId(int)
+ */
+ public int id;
+
+ /**
+ * The pointer tool type.
+ * Initially set to 0.
+ *
+ * @see MotionEvent#getToolType(int)
+ */
+ public int toolType;
+
+ /**
+ * Resets the pointer properties to their initial values.
+ */
+ public void clear() {
+ id = INVALID_POINTER_ID;
+ toolType = TOOL_TYPE_UNKNOWN;
+ }
+
+ /**
+ * Copies the contents of another pointer properties object.
+ *
+ * @param other The pointer properties object to copy.
+ */
+ public void copyFrom(PointerProperties other) {
+ id = other.id;
+ toolType = other.toolType;
+ }
+ }
}
diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java
index 391ba1e..cd48a4f 100755
--- a/core/java/android/view/OrientationEventListener.java
+++ b/core/java/android/view/OrientationEventListener.java
@@ -21,7 +21,6 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.util.Config;
import android.util.Log;
/**
@@ -31,7 +30,7 @@ import android.util.Log;
public abstract class OrientationEventListener {
private static final String TAG = "OrientationEventListener";
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean localLOGV = false;
private int mOrientation = ORIENTATION_UNKNOWN;
private SensorManager mSensorManager;
private boolean mEnabled = false;
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 6a21b5a..5d2c1a7 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -163,6 +163,13 @@ public class ScaleGestureDetector {
private int mActiveId1;
private boolean mActive0MostRecent;
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
ViewConfiguration config = ViewConfiguration.get(context);
mContext = context;
@@ -171,16 +178,20 @@ public class ScaleGestureDetector {
}
public boolean onTouchEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+ }
+
final int action = event.getActionMasked();
- boolean handled = true;
if (action == MotionEvent.ACTION_DOWN) {
reset(); // Start fresh
}
- if (mInvalidGesture) return false;
-
- if (!mGestureInProgress) {
+ boolean handled = true;
+ if (mInvalidGesture) {
+ handled = false;
+ } else if (!mGestureInProgress) {
switch (action) {
case MotionEvent.ACTION_DOWN: {
mActiveId0 = event.getPointerId(0);
@@ -465,6 +476,10 @@ public class ScaleGestureDetector {
break;
}
}
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
return handled;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 3efc799..764899f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -33,7 +33,6 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.ParcelFileDescriptor;
import android.util.AttributeSet;
-import android.util.Config;
import android.util.Log;
import java.lang.ref.WeakReference;
@@ -83,7 +82,7 @@ import java.util.concurrent.locks.ReentrantLock;
public class SurfaceView extends View {
static private final String TAG = "SurfaceView";
static private final boolean DEBUG = false;
- static private final boolean localLOGV = DEBUG ? true : Config.LOGV;
+ static private final boolean localLOGV = DEBUG ? true : false;
final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
@@ -427,7 +426,7 @@ public class SurfaceView extends View {
if (!mHaveFrame) {
return;
}
- ViewRoot viewRoot = (ViewRoot) getRootView().getParent();
+ ViewAncestor viewRoot = (ViewAncestor) getRootView().getParent();
if (viewRoot != null) {
mTranslator = viewRoot.mTranslator;
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
new file mode 100644
index 0000000..755ecf5
--- /dev/null
+++ b/core/java/android/view/TextureView.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.SurfaceTexture;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * <p>A TextureView can be used to display a content stream. Such a content
+ * stream can for instance be a video or an OpenGL scene. The content stream
+ * can come from the application's process as well as a remote process.</p>
+ *
+ * <p>TextureView can only be used in a hardware accelerated window. When
+ * rendered in software, TextureView will draw nothing.</p>
+ *
+ * <p>Unlike {@link SurfaceView}, TextureView does not create a separate
+ * window but behaves as a regular View. This key difference allows a
+ * TextureView to be moved, transformed, animated, etc. For instance, you
+ * can make a TextureView semi-translucent by calling
+ * <code>myView.setAlpha(0.5f)</code>.</p>
+ *
+ * <p>Using a TextureView is simple: all you need to do is get its
+ * {@link SurfaceTexture}. The {@link SurfaceTexture} can then be used to
+ * render content. The following example demonstrates how to render the
+ * camera preview into a TextureView:</p>
+ *
+ * <pre>
+ * public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
+ * private Camera mCamera;
+ * private TextureView mTextureView;
+ *
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ *
+ * mTextureView = new TextureView(this);
+ * mTextureView.setSurfaceTextureListener(this);
+ *
+ * setContentView(mTextureView);
+ * }
+ *
+ * protected void onDestroy() {
+ * super.onDestroy();
+ *
+ * mCamera.stopPreview();
+ * mCamera.release();
+ * }
+ *
+ * public void onSurfaceTextureAvailable(SurfaceTexture surface) {
+ * mCamera = Camera.open();
+ *
+ * try {
+ * mCamera.setPreviewTexture(surface);
+ * mCamera.startPreview();
+ * } catch (IOException ioe) {
+ * // Something bad happened
+ * }
+ * }
+ *
+ * public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ * // Ignored, Camera does all the work for us
+ * }
+ * }
+ * </pre>
+ *
+ * <p>A TextureView's SurfaceTexture can be obtained either by invoking
+ * {@link #getSurfaceTexture()} or by using a {@link SurfaceTextureListener}.
+ * It is important to know that a SurfaceTexture is available only after the
+ * TextureView is attached to a window (and {@link #onAttachedToWindow()} has
+ * been invoked.) It is therefore highly recommended you use a listener to
+ * be notified when the SurfaceTexture becomes available.</p>
+ *
+ * @see SurfaceView
+ * @see SurfaceTexture
+ */
+public class TextureView extends View {
+ private HardwareLayer mLayer;
+ private SurfaceTexture mSurface;
+ private SurfaceTextureListener mListener;
+
+ private final Runnable mUpdateLayerAction = new Runnable() {
+ @Override
+ public void run() {
+ updateLayer();
+ }
+ };
+ private SurfaceTexture.OnFrameAvailableListener mUpdateListener;
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ */
+ public TextureView(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ public TextureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyle The default style to apply to this view. If 0, no style
+ * will be applied (beyond what is included in the theme). This may
+ * either be an attribute resource, whose value will be retrieved
+ * from the current theme, or an explicit style resource.
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ public TextureView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ mLayerPaint = new Paint();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (!isHardwareAccelerated()) {
+ Log.w("TextureView", "A TextureView or a subclass can only be "
+ + "used with hardware acceleration enabled.");
+ }
+ }
+
+ /**
+ * The layer type of a TextureView is ignored since a TextureView is always
+ * considered to act as a hardware layer. The optional paint supplied to this
+ * method will however be taken into account when rendering the content of
+ * this TextureView.
+ *
+ * @param layerType The ype of layer to use with this view, must be one of
+ * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+ * {@link #LAYER_TYPE_HARDWARE}
+ * @param paint The paint used to compose the layer. This argument is optional
+ * and can be null. It is ignored when the layer type is
+ * {@link #LAYER_TYPE_NONE}
+ */
+ @Override
+ public void setLayerType(int layerType, Paint paint) {
+ if (paint != mLayerPaint) {
+ mLayerPaint = paint;
+ invalidate();
+ }
+ }
+
+ /**
+ * Always returns {@link #LAYER_TYPE_HARDWARE}.
+ */
+ @Override
+ public int getLayerType() {
+ return LAYER_TYPE_HARDWARE;
+ }
+
+ /**
+ * Calling this method has no effect.
+ */
+ @Override
+ public void buildLayer() {
+ }
+
+ /**
+ * Subclasses of TextureView cannot do their own rendering
+ * with the {@link Canvas} object.
+ *
+ * @param canvas The Canvas to which the View is rendered.
+ */
+ @Override
+ public final void draw(Canvas canvas) {
+ super.draw(canvas);
+ }
+
+ /**
+ * Subclasses of TextureView cannot do their own rendering
+ * with the {@link Canvas} object.
+ *
+ * @param canvas The Canvas to which the View is rendered.
+ */
+ @Override
+ protected final void onDraw(Canvas canvas) {
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mSurface != null) {
+ nSetDefaultBufferSize(mSurface.mSurfaceTexture, getWidth(), getHeight());
+ }
+ }
+
+ @Override
+ HardwareLayer getHardwareLayer() {
+ if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+ return null;
+ }
+
+ if (mLayer == null) {
+ mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer();
+ mSurface = mAttachInfo.mHardwareRenderer.createSuraceTexture(mLayer);
+ nSetDefaultBufferSize(mSurface.mSurfaceTexture, getWidth(), getHeight());
+
+ mUpdateListener = new SurfaceTexture.OnFrameAvailableListener() {
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ // Per SurfaceTexture's documentation, the callback may be invoked
+ // from an arbitrary thread
+ post(mUpdateLayerAction);
+ }
+ };
+ mSurface.setOnFrameAvailableListener(mUpdateListener);
+
+ if (mListener != null) {
+ mListener.onSurfaceTextureAvailable(mSurface);
+ }
+ }
+
+ return mLayer;
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (mSurface != null) {
+ // When the view becomes invisible, stop updating it, it's a waste of CPU
+ // To cancel updates, the easiest thing to do is simply to remove the
+ // updates listener
+ if (visibility == VISIBLE) {
+ mSurface.setOnFrameAvailableListener(mUpdateListener);
+ updateLayer();
+ } else {
+ mSurface.setOnFrameAvailableListener(null);
+ }
+ }
+ }
+
+ private void updateLayer() {
+ if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+ return;
+ }
+
+ mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight(), mSurface);
+
+ invalidate();
+ }
+
+ /**
+ * Returns the {@link SurfaceTexture} used by this view. This method
+ * may return null if the view is not attached to a window.
+ */
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurface;
+ }
+
+ /**
+ * Returns the {@link SurfaceTextureListener} currently associated with this
+ * texture view.
+ *
+ * @see #setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener)
+ * @see SurfaceTextureListener
+ */
+ public SurfaceTextureListener getSurfaceTextureListener() {
+ return mListener;
+ }
+
+ /**
+ * Sets the {@link SurfaceTextureListener} used to listen to surface
+ * texture events.
+ *
+ * @see #getSurfaceTextureListener()
+ * @see SurfaceTextureListener
+ */
+ public void setSurfaceTextureListener(SurfaceTextureListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * This listener can be used to be notified when the surface texture
+ * associated with this texture view is available.
+ */
+ public static interface SurfaceTextureListener {
+ /**
+ * Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
+ *
+ * @param surface The surface returned by
+ * {@link android.view.TextureView#getSurfaceTexture()}
+ */
+ public void onSurfaceTextureAvailable(SurfaceTexture surface);
+
+ /**
+ * Invoked when the {@link SurfaceTexture}'s buffers size changed.
+ *
+ * @param surface The surface returned by
+ * {@link android.view.TextureView#getSurfaceTexture()}
+ * @param width The new width of the surface
+ * @param height The new height of the surface
+ */
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height);
+ }
+
+ private static native void nSetDefaultBufferSize(int surfaceTexture, int width, int height);
+}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index fccef2b..5a91d31 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -50,6 +50,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
private int mPtr;
private VelocityTracker mNext;
+ private boolean mIsPooled;
private static native int nativeInitialize();
private static native void nativeDispose(int ptr);
@@ -93,6 +94,20 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
return mNext;
}
+ /**
+ * @hide
+ */
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ /**
+ * @hide
+ */
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
+
private VelocityTracker() {
mPtr = nativeInitialize();
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c315884..30ac3f7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,8 @@
package android.view;
+import android.util.FloatProperty;
+import android.util.Property;
import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.android.internal.view.menu.MenuBuilder;
@@ -49,7 +51,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pool;
@@ -62,6 +63,7 @@ import android.view.ContextMenu.ContextMenuInfo;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
@@ -128,11 +130,11 @@ import java.util.concurrent.CopyOnWriteArrayList;
* that will be notified when something interesting happens to the view. For
* example, all views will let you set a listener to be notified when the view
* gains or loses focus. You can register such a listener using
- * {@link #setOnFocusChangeListener}. Other view subclasses offer more
- * specialized listeners. For example, a Button exposes a listener to notify
- * clients when the button is clicked.</li>
+ * {@link #setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}.
+ * Other view subclasses offer more specialized listeners. For example, a Button
+ * exposes a listener to notify clients when the button is clicked.</li>
* <li><strong>Set visibility:</strong> You can hide or show views using
- * {@link #setVisibility}.</li>
+ * {@link #setVisibility(int)}.</li>
* </ul>
* </p>
* <p><em>
@@ -173,61 +175,61 @@ import java.util.concurrent.CopyOnWriteArrayList;
*
* <tr>
* <td rowspan="3">Layout</td>
- * <td><code>{@link #onMeasure}</code></td>
+ * <td><code>{@link #onMeasure(int, int)}</code></td>
* <td>Called to determine the size requirements for this view and all
* of its children.
* </td>
* </tr>
* <tr>
- * <td><code>{@link #onLayout}</code></td>
+ * <td><code>{@link #onLayout(boolean, int, int, int, int)}</code></td>
* <td>Called when this view should assign a size and position to all
* of its children.
* </td>
* </tr>
* <tr>
- * <td><code>{@link #onSizeChanged}</code></td>
+ * <td><code>{@link #onSizeChanged(int, int, int, int)}</code></td>
* <td>Called when the size of this view has changed.
* </td>
* </tr>
*
* <tr>
* <td>Drawing</td>
- * <td><code>{@link #onDraw}</code></td>
+ * <td><code>{@link #onDraw(android.graphics.Canvas)}</code></td>
* <td>Called when the view should render its content.
* </td>
* </tr>
*
* <tr>
* <td rowspan="4">Event processing</td>
- * <td><code>{@link #onKeyDown}</code></td>
+ * <td><code>{@link #onKeyDown(int, KeyEvent)}</code></td>
* <td>Called when a new key event occurs.
* </td>
* </tr>
* <tr>
- * <td><code>{@link #onKeyUp}</code></td>
+ * <td><code>{@link #onKeyUp(int, KeyEvent)}</code></td>
* <td>Called when a key up event occurs.
* </td>
* </tr>
* <tr>
- * <td><code>{@link #onTrackballEvent}</code></td>
+ * <td><code>{@link #onTrackballEvent(MotionEvent)}</code></td>
* <td>Called when a trackball motion event occurs.
* </td>
* </tr>
* <tr>
- * <td><code>{@link #onTouchEvent}</code></td>
+ * <td><code>{@link #onTouchEvent(MotionEvent)}</code></td>
* <td>Called when a touch screen motion event occurs.
* </td>
* </tr>
*
* <tr>
* <td rowspan="2">Focus</td>
- * <td><code>{@link #onFocusChanged}</code></td>
+ * <td><code>{@link #onFocusChanged(boolean, int, android.graphics.Rect)}</code></td>
* <td>Called when the view gains or loses focus.
* </td>
* </tr>
*
* <tr>
- * <td><code>{@link #onWindowFocusChanged}</code></td>
+ * <td><code>{@link #onWindowFocusChanged(boolean)}</code></td>
* <td>Called when the window containing the view gains or loses focus.
* </td>
* </tr>
@@ -246,7 +248,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
* </tr>
*
* <tr>
- * <td><code>{@link #onWindowVisibilityChanged}</code></td>
+ * <td><code>{@link #onWindowVisibilityChanged(int)}</code></td>
* <td>Called when the visibility of the window containing the view
* has changed.
* </td>
@@ -562,15 +564,15 @@ import java.util.concurrent.CopyOnWriteArrayList;
* As a remedy, the framework offers a touch filtering mechanism that can be used to
* improve the security of views that provide access to sensitive functionality.
* </p><p>
- * To enable touch filtering, call {@link #setFilterTouchesWhenObscured} or set the
+ * To enable touch filtering, call {@link #setFilterTouchesWhenObscured(boolean)} or set the
* android:filterTouchesWhenObscured layout attribute to true. When enabled, the framework
* will discard touches that are received whenever the view's window is obscured by
* another visible window. As a result, the view will not receive touches whenever a
* toast, dialog or other window appears above the view's window.
* </p><p>
* For more fine-grained control over security, consider overriding the
- * {@link #onFilterTouchEventForSecurity} method to implement your own security policy.
- * See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
+ * {@link #onFilterTouchEventForSecurity(MotionEvent)} method to implement your own
+ * security policy. See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
* </p>
*
* @attr ref android.R.styleable#View_alpha
@@ -632,7 +634,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
*
* @see android.view.ViewGroup
*/
-public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
+public class View implements Drawable.Callback2, KeyEvent.Callback, AccessibilityEventSource {
private static final boolean DBG = false;
/**
@@ -668,19 +670,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private static final int FITS_SYSTEM_WINDOWS = 0x00000002;
/**
- * This view is visible. Use with {@link #setVisibility}.
+ * This view is visible. Use with {@link #setVisibility(int)}.
*/
public static final int VISIBLE = 0x00000000;
/**
* This view is invisible, but it still takes up space for layout purposes.
- * Use with {@link #setVisibility}.
+ * Use with {@link #setVisibility(int)}.
*/
public static final int INVISIBLE = 0x00000004;
/**
* This view is invisible, and it doesn't take any space for layout
- * purposes. Use with {@link #setVisibility}.
+ * purposes. Use with {@link #setVisibility(int)}.
*/
public static final int GONE = 0x00000008;
@@ -714,10 +716,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
static final int ENABLED_MASK = 0x00000020;
/**
- * This view won't draw. {@link #onDraw} won't be called and further
- * optimizations
- * will be performed. It is okay to have this flag set and a background.
- * Use with DRAW_MASK when calling setFlags.
+ * This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be
+ * called and further optimizations will be performed. It is okay to have
+ * this flag set and a background. Use with DRAW_MASK when calling setFlags.
* {@hide}
*/
static final int WILL_NOT_DRAW = 0x00000080;
@@ -951,6 +952,54 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
static final int PARENT_SAVE_DISABLED_MASK = 0x20000000;
/**
+ * Horizontal direction of this view is from Left to Right.
+ * Use with {@link #setLayoutDirection}.
+ * {@hide}
+ */
+ public static final int LAYOUT_DIRECTION_LTR = 0x00000000;
+
+ /**
+ * Horizontal direction of this view is from Right to Left.
+ * Use with {@link #setLayoutDirection}.
+ * {@hide}
+ */
+ public static final int LAYOUT_DIRECTION_RTL = 0x40000000;
+
+ /**
+ * Horizontal direction of this view is inherited from its parent.
+ * Use with {@link #setLayoutDirection}.
+ * {@hide}
+ */
+ public static final int LAYOUT_DIRECTION_INHERIT = 0x80000000;
+
+ /**
+ * Horizontal direction of this view is from deduced from the default language
+ * script for the locale. Use with {@link #setLayoutDirection}.
+ * {@hide}
+ */
+ public static final int LAYOUT_DIRECTION_LOCALE = 0xC0000000;
+
+ /**
+ * Mask for use with setFlags indicating bits used for horizontalDirection.
+ * {@hide}
+ */
+ static final int LAYOUT_DIRECTION_MASK = 0xC0000000;
+
+ /*
+ * Array of horizontal direction flags for mapping attribute "horizontalDirection" to correct
+ * flag value.
+ * {@hide}
+ */
+ private static final int[] LAYOUT_DIRECTION_FLAGS = {LAYOUT_DIRECTION_LTR,
+ LAYOUT_DIRECTION_RTL, LAYOUT_DIRECTION_INHERIT, LAYOUT_DIRECTION_LOCALE};
+
+ /**
+ * Default horizontalDirection.
+ * {@hide}
+ */
+ private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
+
+ /**
* View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
* should add all focusable Views regardless if they are focusable in touch mode.
*/
@@ -963,34 +1012,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
/**
- * Use with {@link #focusSearch}. Move focus to the previous selectable
+ * Use with {@link #focusSearch(int)}. Move focus to the previous selectable
* item.
*/
public static final int FOCUS_BACKWARD = 0x00000001;
/**
- * Use with {@link #focusSearch}. Move focus to the next selectable
+ * Use with {@link #focusSearch(int)}. Move focus to the next selectable
* item.
*/
public static final int FOCUS_FORWARD = 0x00000002;
/**
- * Use with {@link #focusSearch}. Move focus to the left.
+ * Use with {@link #focusSearch(int)}. Move focus to the left.
*/
public static final int FOCUS_LEFT = 0x00000011;
/**
- * Use with {@link #focusSearch}. Move focus up.
+ * Use with {@link #focusSearch(int)}. Move focus up.
*/
public static final int FOCUS_UP = 0x00000021;
/**
- * Use with {@link #focusSearch}. Move focus to the right.
+ * Use with {@link #focusSearch(int)}. Move focus to the right.
*/
public static final int FOCUS_RIGHT = 0x00000042;
/**
- * Use with {@link #focusSearch}. Move focus down.
+ * Use with {@link #focusSearch(int)}. Move focus down.
*/
public static final int FOCUS_DOWN = 0x00000082;
@@ -1304,6 +1353,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
static final int VIEW_STATE_PRESSED = 1 << 4;
static final int VIEW_STATE_ACTIVATED = 1 << 5;
static final int VIEW_STATE_ACCELERATED = 1 << 6;
+ static final int VIEW_STATE_HOVERED = 1 << 7;
+ static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
+ static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
static final int[] VIEW_STATE_IDS = new int[] {
R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED,
@@ -1313,6 +1365,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
R.attr.state_pressed, VIEW_STATE_PRESSED,
R.attr.state_activated, VIEW_STATE_ACTIVATED,
R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
+ R.attr.state_hovered, VIEW_STATE_HOVERED,
+ R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
+ R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED,
};
static {
@@ -1440,6 +1495,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private static final Object sTagsLock = new Object();
/**
+ * The next available accessiiblity id.
+ */
+ private static int sNextAccessibilityViewId;
+
+ /**
* The animation currently associated with this view.
* @hide
*/
@@ -1481,6 +1541,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
int mID = NO_ID;
/**
+ * The stable ID of this view for accessibility porposes.
+ */
+ int mAccessibilityViewId = NO_ID;
+
+ /**
* The view's tag.
* {@hide}
*
@@ -1623,6 +1688,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/**
+ * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT.
+ * @hide
+ */
+ private static final int HOVERED = 0x10000000;
+
+ /**
* Indicates that pivotX or pivotY were explicitly set and we should not assume the center
* for transform operations
*
@@ -1643,6 +1714,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
static final int INVALIDATED = 0x80000000;
+ /* Masks for mPrivateFlags2 */
+
+ /**
+ * Indicates that this view has reported that it can accept the current drag's content.
+ * Cleared when the drag operation concludes.
+ * @hide
+ */
+ static final int DRAG_CAN_ACCEPT = 0x00000001;
+
+ /**
+ * Indicates that this view is currently directly under the drag location in a
+ * drag-and-drop operation involving content that it can accept. Cleared when
+ * the drag exits the view, or when the drag operation concludes.
+ * @hide
+ */
+ static final int DRAG_HOVERED = 0x00000002;
+
+ /**
+ * Indicates whether the view is drawn in right-to-left direction.
+ *
+ * @hide
+ */
+ static final int RESOLVED_LAYOUT_RTL = 0x00000004;
+
+ /* End of masks for mPrivateFlags2 */
+
+ static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED;
+
/**
* Always allow a user to over-scroll this view, provided it is a
* view that can scroll.
@@ -1814,6 +1913,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
@ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY, name = "DIRTY")
})
int mPrivateFlags;
+ int mPrivateFlags2;
/**
* This view's request for the visibility of the status bar.
@@ -2243,12 +2343,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private ViewPropertyAnimator mAnimator = null;
/**
- * Cache drag/drop state
- *
- */
- boolean mCanAcceptDrop;
-
- /**
* Flag indicating that a drag can cross window boundaries. When
* {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
* with this flag set, all visible applications will be able to participate
@@ -2356,6 +2450,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
Rect mLocalDirtyRect;
/**
+ * Consistency verifier for debugging purposes.
+ * @hide
+ */
+ protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -2364,7 +2466,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
- mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
+ mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | LAYOUT_DIRECTION_INHERIT;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
}
@@ -2562,6 +2664,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
viewFlagMasks |= VISIBILITY_MASK;
}
break;
+ case com.android.internal.R.styleable.View_layoutDirection:
+ // Clear any HORIZONTAL_DIRECTION flag already set
+ viewFlagValues &= ~LAYOUT_DIRECTION_MASK;
+ // Set the HORIZONTAL_DIRECTION flags depending on the value of the attribute
+ final int layoutDirection = a.getInt(attr, -1);
+ if (layoutDirection != -1) {
+ viewFlagValues |= LAYOUT_DIRECTION_FLAGS[layoutDirection];
+ } else {
+ // Set to default (LAYOUT_DIRECTION_INHERIT)
+ viewFlagValues |= LAYOUT_DIRECTION_DEFAULT;
+ }
+ viewFlagMasks |= LAYOUT_DIRECTION_MASK;
+ break;
case com.android.internal.R.styleable.View_drawingCacheQuality:
final int cacheQuality = a.getInt(attr, 0);
if (cacheQuality != 0) {
@@ -2799,8 +2914,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Set the size of the faded edge used to indicate that more content in this
* view is available. Will not change whether the fading edge is enabled; use
- * {@link #setVerticalFadingEdgeEnabled} or {@link #setHorizontalFadingEdgeEnabled}
- * to enable the fading edge for the vertical or horizontal fading edges.
+ * {@link #setVerticalFadingEdgeEnabled(boolean)} or
+ * {@link #setHorizontalFadingEdgeEnabled(boolean)} to enable the fading edge
+ * for the vertical or horizontal fading edges.
*
* @param length The size in pixels of the faded edge used to indicate that more
* content in this view is visible.
@@ -3137,6 +3253,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Performs button-related actions during a touch down event.
+ *
+ * @param event The event.
+ * @return True if the down was consumed.
+ *
+ * @hide
+ */
+ protected boolean performButtonActionOnTouchDown(MotionEvent event) {
+ if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
+ if (showContextMenu(event.getX(), event.getY(), event.getMetaState())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Bring up the context menu for this view.
*
* @return Whether a context menu was displayed.
@@ -3146,6 +3279,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Bring up the context menu for this view, referring to the item under the specified point.
+ *
+ * @param x The referenced x coordinate.
+ * @param y The referenced y coordinate.
+ * @param metaState The keyboard modifiers that were pressed.
+ * @return Whether a context menu was displayed.
+ *
+ * @hide
+ */
+ public boolean showContextMenu(float x, float y, int metaState) {
+ return showContextMenu();
+ }
+
+ /**
* Start an action mode.
*
* @param callback Callback that will control the lifecycle of the action mode
@@ -3193,7 +3340,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * Give this view focus. This will cause {@link #onFocusChanged} to be called.
+ * Give this view focus. This will cause
+ * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
*
* Note: this does not check whether this {@link View} should get focus, it just
* gives it focus no matter what. It should only be called internally by framework
@@ -3278,7 +3426,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Called when this view wants to give up focus. This will cause
- * {@link #onFocusChanged} to be called.
+ * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
*/
public void clearFocus() {
if (DBG) {
@@ -3404,7 +3552,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * {@inheritDoc}
+ * Sends an accessibility event of the given type. If accessiiblity is
+ * not enabled this method has no effect. The default implementation calls
+ * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first
+ * to populate information about the event source (this View), then calls
+ * {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)} to
+ * populate the text content of the event source including its descendants,
+ * and last calls
+ * {@link ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+ * on its parent to resuest sending of the event to interested parties.
+ *
+ * @param eventType The type of the event to send.
+ *
+ * @see #onInitializeAccessibilityEvent(AccessibilityEvent)
+ * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ * @see ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)
*/
public void sendAccessibilityEvent(int eventType) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
@@ -3413,12 +3575,94 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * {@inheritDoc}
+ * This method behaves exactly as {@link #sendAccessibilityEvent(int)} but
+ * takes as an argument an empty {@link AccessibilityEvent} and does not
+ * perfrom a check whether accessibility is enabled.
+ *
+ * @param event The event to send.
+ *
+ * @see #sendAccessibilityEvent(int)
*/
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (!isShown()) {
return;
}
+ onInitializeAccessibilityEvent(event);
+ dispatchPopulateAccessibilityEvent(event);
+ // In the beginning we called #isShown(), so we know that getParent() is not null.
+ getParent().requestSendAccessibilityEvent(this, event);
+ }
+
+ /**
+ * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then
+ * to its children for adding their text content to the event. Note that the
+ * event text is populated in a separate dispatch path since we add to the
+ * event not only the text of the source but also the text of all its descendants.
+ * </p>
+ * A typical implementation will call
+ * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on the this view
+ * and then call the {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+ * on each child. Override this method if custom population of the event text
+ * content is required.
+ *
+ * @param event The event.
+ *
+ * @return True if the event population was completed.
+ */
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return false;
+ }
+
+ /**
+ * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+ * giving a chance to this View to populate the accessibility event with its
+ * text content. While the implementation is free to modify other event
+ * attributes this should be performed in
+ * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}.
+ * <p>
+ * Example: Adding formatted date string to an accessibility event in addition
+ * to the text added by the super implementation.
+ * </p><p><pre><code>
+ * public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ * super.onPopulateAccessibilityEvent(event);
+ * final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
+ * String selectedDateUtterance = DateUtils.formatDateTime(mContext,
+ * mCurrentDate.getTimeInMillis(), flags);
+ * event.getText().add(selectedDateUtterance);
+ * }
+ * </code></pre></p>
+ *
+ * @param event The accessibility event which to populate.
+ *
+ * @see #sendAccessibilityEvent(int)
+ * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ */
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+
+ }
+
+ /**
+ * Initializes an {@link AccessibilityEvent} with information about the
+ * the type of the event and this View which is the event source. In other
+ * words, the source of an accessibility event is the view whose state
+ * change triggered firing the event.
+ * <p>
+ * Example: Setting the password property of an event in addition
+ * to properties set by the super implementation.
+ * </p><p><pre><code>
+ * public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ * super.onInitializeAccessibilityEvent(event);
+ * event.setPassword(true);
+ * }
+ * </code></pre></p>
+ * @param event The event to initialeze.
+ *
+ * @see #sendAccessibilityEvent(int)
+ * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ */
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setSource(this);
event.setClassName(getClass().getName());
event.setPackageName(getContext().getPackageName());
event.setEnabled(isEnabled());
@@ -3431,22 +3675,112 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
event.setCurrentItemIndex(focusablesTempList.indexOf(this));
focusablesTempList.clear();
}
+ }
- dispatchPopulateAccessibilityEvent(event);
+ /**
+ * Returns an {@link AccessibilityNodeInfo} representing this 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 this view to
+ * initialize the former.
+ * <p>
+ * Note: The client is responsible for recycling the obtained instance by calling
+ * {@link AccessibilityNodeInfo#recycle()} to minimize object creation.
+ * </p>
+ * @return A populated {@link AccessibilityNodeInfo}.
+ *
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(this);
+ onInitializeAccessibilityNodeInfo(info);
+ return info;
+ }
- AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
+ /**
+ * Initializes an {@link AccessibilityNodeInfo} with information about this view.
+ * The base implementation sets:
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#setParent(View)},</li>
+ * <li>{@link AccessibilityNodeInfo#setBounds(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setClassName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setEnabled(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setFocusable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setFocused(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setLongClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setSelected(boolean)},</li>
+ * </ul>
+ * <p>
+ * Subclasses should override this method, call the super implementation,
+ * and set additional attributes.
+ * </p>
+ * @param info The instance to initialize.
+ */
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ Rect bounds = mAttachInfo.mTmpInvalRect;
+ getDrawingRect(bounds);
+ info.setBounds(bounds);
+
+ ViewParent parent = getParent();
+ if (parent instanceof View) {
+ View parentView = (View) parent;
+ info.setParent(parentView);
+ }
+
+ info.setPackageName(mContext.getPackageName());
+ info.setClassName(getClass().getName());
+ info.setContentDescription(getContentDescription());
+
+ info.setEnabled(isEnabled());
+ info.setClickable(isClickable());
+ info.setFocusable(isFocusable());
+ info.setFocused(isFocused());
+ info.setSelected(isSelected());
+ info.setLongClickable(isLongClickable());
+
+ // TODO: These make sense only if we are in an AdapterView but all
+ // views can be selected. Maybe from accessiiblity perspective
+ // we should report as selectable view in an AdapterView.
+ info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+
+ if (isFocusable()) {
+ if (isFocused()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+ } else {
+ info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ }
+ }
}
/**
- * Dispatches an {@link AccessibilityEvent} to the {@link View} children
- * to be populated.
+ * Gets the unique identifier of this view on the screen for accessibility purposes.
+ * If this {@link View} is not attached to any window, {@value #NO_ID} is returned.
*
- * @param event The event.
+ * @return The view accessibility id.
*
- * @return True if the event population was completed.
+ * @hide
*/
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- return false;
+ public int getAccessibilityViewId() {
+ if (mAccessibilityViewId == NO_ID) {
+ mAccessibilityViewId = sNextAccessibilityViewId++;
+ }
+ return mAccessibilityViewId;
+ }
+
+ /**
+ * Gets the unique identifier of the window in which this View reseides.
+ *
+ * @return The window accessibility id.
+ *
+ * @hide
+ */
+ public int getAccessibilityWindowId() {
+ return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId : NO_ID;
}
/**
@@ -3924,11 +4258,47 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Returns the layout direction for this view.
+ *
+ * @return One of {@link #LAYOUT_DIRECTION_LTR},
+ * {@link #LAYOUT_DIRECTION_RTL},
+ * {@link #LAYOUT_DIRECTION_INHERIT} or
+ * {@link #LAYOUT_DIRECTION_LOCALE}.
+ * @attr ref android.R.styleable#View_layoutDirection
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "LTR"),
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RTL"),
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"),
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE")
+ })
+ public int getLayoutDirection() {
+ return mViewFlags & LAYOUT_DIRECTION_MASK;
+ }
+
+ /**
+ * Set the layout direction for this view.
+ *
+ * @param layoutDirection One of {@link #LAYOUT_DIRECTION_LTR},
+ * {@link #LAYOUT_DIRECTION_RTL},
+ * {@link #LAYOUT_DIRECTION_INHERIT} or
+ * {@link #LAYOUT_DIRECTION_LOCALE}.
+ * @attr ref android.R.styleable#View_layoutDirection
+ * @hide
+ */
+ @RemotableViewMethod
+ public void setLayoutDirection(int layoutDirection) {
+ setFlags(layoutDirection, LAYOUT_DIRECTION_MASK);
+ }
+
+ /**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
- * Typically, if you override {@link #onDraw} you should clear this flag.
+ * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
+ * you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
@@ -4057,7 +4427,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* {@link #setPressed(boolean)} is explicitly called, only clickable views can enter
* the pressed state.
*
- * @see #setPressed
+ * @see #setPressed(boolean)
* @see #isClickable()
* @see #setClickable(boolean)
*
@@ -4084,7 +4454,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Controls whether the saving of this view's state is
* enabled (that is, whether its {@link #onSaveInstanceState} method
* will be called). Note that even if freezing is enabled, the
- * view still must have an id assigned to it (via {@link #setId setId()})
+ * view still must have an id assigned to it (via {@link #setId(int)})
* for its state to be saved. This flag can only disable the
* saving of this view; any child views may still have their state saved.
*
@@ -4321,6 +4691,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Finds the Views that contain given text. The containment is case insensitive.
+ * As View's text is considered any text content that View renders.
+ *
+ * @param outViews The output list of matching Views.
+ * @param text The text to match against.
+ */
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
+ }
+
+ /**
* Find and return all touchable views that are descendants of this view,
* possibly including this view if it is touchable itself.
*
@@ -4355,7 +4735,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* false), or if it is focusable and it is not focusable in touch mode
* ({@link #isFocusableInTouchMode}) while the device is in touch mode.
*
- * See also {@link #focusSearch}, which is what you call to say that you
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
* This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments
@@ -4376,7 +4756,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* false), or if it is focusable and it is not focusable in touch mode
* ({@link #isFocusableInTouchMode}) while the device is in touch mode.
*
- * See also {@link #focusSearch}, which is what you call to say that you
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
* This is equivalent to calling {@link #requestFocus(int, Rect)} with
@@ -4406,7 +4786,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* {@link android.view.ViewGroup#getDescendantFocusability()} equal to
* {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}.
*
- * See also {@link #focusSearch}, which is what you call to say that you
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
* You may wish to override this method if your custom {@link View} has an internal
@@ -4427,8 +4807,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
// need to be focusable in touch mode if in touch mode
if (isInTouchMode() &&
- (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
- return false;
+ (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
+ return false;
}
// need to not have any parents blocking us
@@ -4440,10 +4820,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return true;
}
- /** Gets the ViewRoot, or null if not attached. */
- /*package*/ ViewRoot getViewRoot() {
+ /** Gets the ViewAncestor, or null if not attached. */
+ /*package*/ ViewAncestor getViewAncestor() {
View root = getRootView();
- return root != null ? (ViewRoot)root.getParent() : null;
+ return root != null ? (ViewAncestor)root.getParent() : null;
}
/**
@@ -4459,7 +4839,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
public final boolean requestFocusFromTouch() {
// Leave touch mode if we need to
if (isInTouchMode()) {
- ViewRoot viewRoot = getViewRoot();
+ ViewAncestor viewRoot = getViewAncestor();
if (viewRoot != null) {
viewRoot.ensureTouchMode(false);
}
@@ -4516,22 +4896,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * capture information of this view for later analysis: developement only
- * check dynamic switch to make sure we only dump view
- * when ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW) is set
- */
- private static void captureViewInfo(String subTag, View v) {
- if (v == null || SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW, 0) == 0) {
- return;
- }
- ViewDebug.dumpCapturedView(subTag, v);
- }
-
- /**
* Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}
* for this view's window. Returns null if the view is not currently attached
* to the window. Normally you will not need to use this directly, but
- * just use the standard high-level event callbacks like {@link #onKeyDown}.
+ * just use the standard high-level event callbacks like
+ * {@link #onKeyDown(int, KeyEvent)}.
*/
public KeyEvent.DispatcherState getKeyDispatcherState() {
return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;
@@ -4562,21 +4931,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if the event was handled, false otherwise.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
- // If any attached key listener a first crack at the event.
-
- //noinspection SimplifiableIfStatement,deprecation
- if (android.util.Config.LOGV) {
- captureViewInfo("captureViewKeyEvent", this);
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
+ // Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
- return event.dispatch(this, mAttachInfo != null
- ? mAttachInfo.mKeyDispatchState : null, this);
+ if (event.dispatch(this, mAttachInfo != null
+ ? mAttachInfo.mKeyDispatchState : null, this)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4597,16 +4971,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
- if (!onFilterTouchEventForSecurity(event)) {
- return false;
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
- //noinspection SimplifiableIfStatement
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
+ if (onFilterTouchEventForSecurity(event)) {
+ //noinspection SimplifiableIfStatement
+ if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
+ mOnTouchListener.onTouch(this, event)) {
+ return true;
+ }
+
+ if (onTouchEvent(event)) {
+ return true;
+ }
}
- return onTouchEvent(event);
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4634,8 +5018,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
+ }
+
//Log.i("view", "view=" + this + ", " + event.toString());
- return onTrackballEvent(event);
+ if (onTrackballEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4643,28 +5038,101 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* <p>
* Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
* are delivered to the view under the pointer. All other generic motion events are
- * delivered to the focused view.
+ * delivered to the focused view. Hover events are handled specially and are delivered
+ * to {@link #onHoverEvent(MotionEvent)}.
* </p>
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ }
+
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE
+ || action == MotionEvent.ACTION_HOVER_EXIT) {
+ if (dispatchHoverEvent(event)) {
+ return true;
+ }
+ } else if (dispatchGenericPointerEvent(event)) {
+ return true;
+ }
+ } else if (dispatchGenericFocusedEvent(event)) {
+ return true;
+ }
+
//noinspection SimplifiableIfStatement
if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnGenericMotionListener.onGenericMotion(this, event)) {
return true;
}
- return onGenericMotionEvent(event);
+ if (onGenericMotionEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch a hover event.
+ * <p>
+ * Do not call this method directly.
+ * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ * @hide
+ */
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ return onHoverEvent(event);
+ }
+
+ /**
+ * Dispatch a generic motion event to the view under the first pointer.
+ * <p>
+ * Do not call this method directly.
+ * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ * @hide
+ */
+ protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Dispatch a generic motion event to the currently focused view.
+ * <p>
+ * Do not call this method directly.
+ * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ * @hide
+ */
+ protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
+ return false;
}
/**
* Dispatch a pointer event.
* <p>
- * Dispatches touch related pointer events to {@link #onTouchEvent} and all
- * other events to {@link #onGenericMotionEvent}. This separation of concerns
- * reinforces the invariant that {@link #onTouchEvent} is really about touches
+ * Dispatches touch related pointer events to {@link #onTouchEvent(MotionEvent)} and all
+ * other events to {@link #onGenericMotionEvent(MotionEvent)}. This separation of concerns
+ * reinforces the invariant that {@link #onTouchEvent(MotionEvent)} is really about touches
* and should not be expected to handle other pointing device features.
* </p>
*
@@ -4789,7 +5257,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @param visibility The new visibility of the window.
*
- * @see #onWindowVisibilityChanged
+ * @see #onWindowVisibilityChanged(int)
*/
public void dispatchWindowVisibilityChanged(int visibility) {
onWindowVisibilityChanged(visibility);
@@ -4865,7 +5333,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @param newConfig The new resource configuration.
*
- * @see #onConfigurationChanged
+ * @see #onConfigurationChanged(android.content.res.Configuration)
*/
public void dispatchConfigurationChanged(Configuration newConfig) {
onConfigurationChanged(newConfig);
@@ -4926,7 +5394,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (mAttachInfo != null) {
return mAttachInfo.mInTouchMode;
} else {
- return ViewRoot.isInTouchMode();
+ return ViewAncestor.isInTouchMode();
}
}
@@ -4981,9 +5449,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
(mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
(event.getRepeatCount() == 0)) {
setPressed(true);
- if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
- postCheckForLongClick(0);
- }
+ checkForLongClick(0);
return true;
}
break;
@@ -5223,15 +5689,80 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* </code>
*
* @param event The generic motion event being processed.
- *
- * @return Return true if you have consumed the event, false if you haven't.
- * The default implementation always returns false.
+ * @return True if the event was handled, false otherwise.
*/
public boolean onGenericMotionEvent(MotionEvent event) {
return false;
}
/**
+ * Implement this method to handle hover events.
+ * <p>
+ * Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER},
+ * {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}.
+ * </p><p>
+ * The view receives hover enter as the pointer enters the bounds of the view and hover
+ * exit as the pointer exits the bound of the view or just before the pointer goes down
+ * (which implies that {@link #onTouchEvent(MotionEvent)} will be called soon).
+ * </p><p>
+ * If the view would like to handle the hover event itself and prevent its children
+ * from receiving hover, it should return true from this method. If this method returns
+ * true and a child has already received a hover enter event, the child will
+ * automatically receive a hover exit event.
+ * </p><p>
+ * The default implementation sets the hovered state of the view if the view is
+ * clickable.
+ * </p>
+ *
+ * @param event The motion event that describes the hover.
+ * @return True if this view handled the hover event and does not want its children
+ * to receive the hover event.
+ */
+ public boolean onHoverEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ setHovered(true);
+ break;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ setHovered(false);
+ break;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the view is currently hovered.
+ *
+ * @return True if the view is currently hovered.
+ */
+ public boolean isHovered() {
+ return (mPrivateFlags & HOVERED) != 0;
+ }
+
+ /**
+ * Sets whether the view is currently hovered.
+ *
+ * @param hovered True if the view is hovered.
+ */
+ public void setHovered(boolean hovered) {
+ if (hovered) {
+ if ((mPrivateFlags & HOVERED) == 0) {
+ mPrivateFlags |= HOVERED;
+ refreshDrawableState();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ }
+ } else {
+ if ((mPrivateFlags & HOVERED) != 0) {
+ mPrivateFlags &= ~HOVERED;
+ refreshDrawableState();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ }
+ }
+ }
+
+ /**
* Implement this method to handle touch screen motion events.
*
* @param event The motion event.
@@ -5241,6 +5772,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
+ if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
+ mPrivateFlags &= ~PRESSED;
+ refreshDrawableState();
+ }
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
@@ -5309,12 +5844,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
break;
case MotionEvent.ACTION_DOWN:
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+
+ if (performButtonActionOnTouchDown(event)) {
+ break;
+ }
+
+ // Walk up the hierarchy to determine if we're inside a scrolling container.
+ boolean isInScrollingContainer = false;
+ ViewParent p = getParent();
+ while (p != null && p instanceof ViewGroup) {
+ if (((ViewGroup) p).shouldDelayChildPressedState()) {
+ isInScrollingContainer = true;
+ break;
+ }
+ p = p.getParent();
+ }
+
+ // For views inside a scrolling container, delay the pressed feedback for
+ // a short period in case this is a scroll.
+ if (isInScrollingContainer) {
+ mPrivateFlags |= PREPRESSED;
+ if (mPendingCheckForTap == null) {
+ mPendingCheckForTap = new CheckForTap();
+ }
+ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+ } else {
+ // Not inside a scrolling container, so show the feedback right away
+ mPrivateFlags |= PRESSED;
+ refreshDrawableState();
+ checkForLongClick(0);
+ }
break;
case MotionEvent.ACTION_CANCEL:
@@ -5544,6 +6104,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mParent.recomputeViewAttributes(this);
}
}
+
+ if ((changed & LAYOUT_DIRECTION_MASK) != 0) {
+ requestLayout();
+ }
}
/**
@@ -5631,6 +6195,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Set the horizontal scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param value the x position to scroll to
+ */
+ public void setScrollX(int value) {
+ scrollTo(value, mScrollY);
+ }
+
+ /**
+ * Set the vertical scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param value the y position to scroll to
+ */
+ public void setScrollY(int value) {
+ scrollTo(mScrollX, value);
+ }
+
+ /**
* Return the scrolled left position of this view. This is the left edge of
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
@@ -5700,7 +6284,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Return the full width measurement information for this view as computed
- * by the most recent call to {@link #measure}. This result is a bit mask
+ * by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
* {@link #getWidth()} to see how wide a view is after layout.
@@ -5724,7 +6308,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Return the full height measurement information for this view as computed
- * by the most recent call to {@link #measure}. This result is a bit mask
+ * by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
* {@link #getHeight()} to see how wide a view is after layout.
@@ -6693,9 +7277,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* When a view has focus and the user navigates away from it, the next view is searched for
* starting from the rectangle filled in by this method.
*
- * By default, the rectange is the {@link #getDrawingRect})of the view. However, if your
- * view maintains some idea of internal selection, such as a cursor, or a selected row
- * or column, you should override this method and fill in a more specific rectangle.
+ * By default, the rectange is the {@link #getDrawingRect(android.graphics.Rect)})
+ * of the view. However, if your view maintains some idea of internal selection,
+ * such as a cursor, or a selected row or column, you should override this method and
+ * fill in a more specific rectangle.
*
* @param r The rectangle to fill in, in this view's coordinates.
*/
@@ -7062,9 +7647,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Mark the the area defined by dirty as needing to be drawn. If the view is
- * visible, {@link #onDraw} will be called at some point in the future.
- * This must be called from a UI thread. To call from a non-UI thread, call
- * {@link #postInvalidate()}.
+ * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some point
+ * in the future. This must be called from a UI thread. To call from a non-UI
+ * thread, call {@link #postInvalidate()}.
*
* WARNING: This method is destructive to dirty.
* @param dirty the rectangle representing the bounds of the dirty region
@@ -7085,7 +7670,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {
// fast-track for GL-enabled applications; just invalidate the whole hierarchy
- // with a null dirty rect, which tells the ViewRoot to redraw everything
+ // with a null dirty rect, which tells the ViewAncestor to redraw everything
p.invalidateChild(this, null);
return;
}
@@ -7104,9 +7689,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Mark the the area defined by the rect (l,t,r,b) as needing to be drawn.
* The coordinates of the dirty rect are relative to the view.
- * If the view is visible, {@link #onDraw} will be called at some point
- * in the future. This must be called from a UI thread. To call
- * from a non-UI thread, call {@link #postInvalidate()}.
+ * If the view is visible, {@link #onDraw(android.graphics.Canvas)}
+ * will be called at some point in the future. This must be called from
+ * a UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
* @param l the left position of the dirty region
* @param t the top position of the dirty region
* @param r the right position of the dirty region
@@ -7128,7 +7713,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {
// fast-track for GL-enabled applications; just invalidate the whole hierarchy
- // with a null dirty rect, which tells the ViewRoot to redraw everything
+ // with a null dirty rect, which tells the ViewAncestor to redraw everything
p.invalidateChild(this, null);
return;
}
@@ -7144,9 +7729,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * Invalidate the whole view. If the view is visible, {@link #onDraw} will
- * be called at some point in the future. This must be called from a
- * UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
+ * Invalidate the whole view. If the view is visible,
+ * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
+ * the future. This must be called from a UI thread. To call from a non-UI thread,
+ * call {@link #postInvalidate()}.
*/
public void invalidate() {
invalidate(true);
@@ -7183,7 +7769,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {
// fast-track for GL-enabled applications; just invalidate the whole hierarchy
- // with a null dirty rect, which tells the ViewRoot to redraw everything
+ // with a null dirty rect, which tells the ViewAncestor to redraw everything
p.invalidateChild(this, null);
return;
}
@@ -7212,8 +7798,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mPrivateFlags &= ~DRAWN;
mPrivateFlags |= INVALIDATED;
mPrivateFlags &= ~DRAWING_CACHE_VALID;
- if (mParent != null && mAttachInfo != null && mAttachInfo.mHardwareAccelerated) {
- mParent.invalidateChild(this, null);
+ if (mParent != null && mAttachInfo != null) {
+ if (mAttachInfo.mHardwareAccelerated) {
+ mParent.invalidateChild(this, null);
+ } else {
+ final Rect r = mAttachInfo.mTmpInvalRect;
+ r.set(0, 0, mRight - mLeft, mBottom - mTop);
+ // Don't call invalidate -- we don't want to internally scroll
+ // our own bounds
+ mParent.invalidateChild(this, r);
+ }
}
}
}
@@ -7319,11 +7913,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
public boolean post(Runnable action) {
Handler handler;
- if (mAttachInfo != null) {
- handler = mAttachInfo.mHandler;
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ handler = attachInfo.mHandler;
} else {
// Assume that post will succeed later
- ViewRoot.getRunQueue().post(action);
+ ViewAncestor.getRunQueue().post(action);
return true;
}
@@ -7348,11 +7943,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
public boolean postDelayed(Runnable action, long delayMillis) {
Handler handler;
- if (mAttachInfo != null) {
- handler = mAttachInfo.mHandler;
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ handler = attachInfo.mHandler;
} else {
// Assume that post will succeed later
- ViewRoot.getRunQueue().postDelayed(action, delayMillis);
+ ViewAncestor.getRunQueue().postDelayed(action, delayMillis);
return true;
}
@@ -7371,11 +7967,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
public boolean removeCallbacks(Runnable action) {
Handler handler;
- if (mAttachInfo != null) {
- handler = mAttachInfo.mHandler;
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ handler = attachInfo.mHandler;
} else {
// Assume that post will succeed later
- ViewRoot.getRunQueue().removeCallbacks(action);
+ ViewAncestor.getRunQueue().removeCallbacks(action);
return true;
}
@@ -7419,11 +8016,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
- if (mAttachInfo != null) {
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
Message msg = Message.obtain();
msg.what = AttachInfo.INVALIDATE_MSG;
msg.obj = this;
- mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
}
@@ -7443,7 +8041,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
- if (mAttachInfo != null) {
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
info.target = this;
info.left = left;
@@ -7454,7 +8053,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final Message msg = Message.obtain();
msg.what = AttachInfo.INVALIDATE_RECT_MSG;
msg.obj = info;
- mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
}
@@ -8046,9 +8645,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* This is called when the view is attached to a window. At this point it
* has a Surface and will start drawing. Note that this function is
- * guaranteed to be called before {@link #onDraw}, however it may be called
- * any time before the first onDraw -- including before or after
- * {@link #onMeasure}.
+ * guaranteed to be called before {@link #onDraw(android.graphics.Canvas)},
+ * however it may be called any time before the first onDraw -- including
+ * before or after {@link #onMeasure(int, int)}.
*
* @see #onDetachedFromWindow()
*/
@@ -8061,6 +8660,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH;
}
jumpDrawablesToCurrentState();
+ resolveLayoutDirection();
+ }
+
+ /**
+ * Resolving the layout direction. LTR is set initially.
+ * We are supposing here that the parent directionality will be resolved before its children
+ */
+ private void resolveLayoutDirection() {
+ mPrivateFlags2 &= ~RESOLVED_LAYOUT_RTL;
+ switch (getLayoutDirection()) {
+ case LAYOUT_DIRECTION_INHERIT:
+ // If this is root view, no need to look at parent's layout dir.
+ if (mParent != null && mParent instanceof ViewGroup &&
+ ((ViewGroup) mParent).isLayoutRtl()) {
+ mPrivateFlags2 |= RESOLVED_LAYOUT_RTL;
+ }
+ break;
+ case LAYOUT_DIRECTION_RTL:
+ mPrivateFlags2 |= RESOLVED_LAYOUT_RTL;
+ break;
+ }
}
/**
@@ -8221,24 +8841,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @param container The SparseArray in which to save the view's state.
*
- * @see #restoreHierarchyState
- * @see #dispatchSaveInstanceState
- * @see #onSaveInstanceState
+ * @see #restoreHierarchyState(android.util.SparseArray)
+ * @see #dispatchSaveInstanceState(android.util.SparseArray)
+ * @see #onSaveInstanceState()
*/
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
/**
- * Called by {@link #saveHierarchyState} to store the state for this view and its children.
- * May be overridden to modify how freezing happens to a view's children; for example, some
- * views may want to not store state for their children.
+ * Called by {@link #saveHierarchyState(android.util.SparseArray)} to store the state for
+ * this view and its children. May be overridden to modify how freezing happens to a
+ * view's children; for example, some views may want to not store state for their children.
*
* @param container The SparseArray in which to save the view's state.
*
- * @see #dispatchRestoreInstanceState
- * @see #saveHierarchyState
- * @see #onSaveInstanceState
+ * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+ * @see #saveHierarchyState(android.util.SparseArray)
+ * @see #onSaveInstanceState()
*/
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
@@ -8272,9 +8892,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return Returns a Parcelable object containing the view's current dynamic
* state, or null if there is nothing interesting to save. The
* default implementation returns null.
- * @see #onRestoreInstanceState
- * @see #saveHierarchyState
- * @see #dispatchSaveInstanceState
+ * @see #onRestoreInstanceState(android.os.Parcelable)
+ * @see #saveHierarchyState(android.util.SparseArray)
+ * @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #setSaveEnabled(boolean)
*/
protected Parcelable onSaveInstanceState() {
@@ -8287,24 +8907,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @param container The SparseArray which holds previously frozen states.
*
- * @see #saveHierarchyState
- * @see #dispatchRestoreInstanceState
- * @see #onRestoreInstanceState
+ * @see #saveHierarchyState(android.util.SparseArray)
+ * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+ * @see #onRestoreInstanceState(android.os.Parcelable)
*/
public void restoreHierarchyState(SparseArray<Parcelable> container) {
dispatchRestoreInstanceState(container);
}
/**
- * Called by {@link #restoreHierarchyState} to retrieve the state for this view and its
- * children. May be overridden to modify how restoreing happens to a view's children; for
- * example, some views may want to not store state for their children.
+ * Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
+ * state for this view and its children. May be overridden to modify how restoring
+ * happens to a view's children; for example, some views may want to not store state
+ * for their children.
*
* @param container The SparseArray which holds previously saved state.
*
- * @see #dispatchSaveInstanceState
- * @see #restoreHierarchyState
- * @see #onRestoreInstanceState
+ * @see #dispatchSaveInstanceState(android.util.SparseArray)
+ * @see #restoreHierarchyState(android.util.SparseArray)
+ * @see #onRestoreInstanceState(android.os.Parcelable)
*/
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) {
@@ -8330,9 +8951,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @param state The frozen state that had previously been returned by
* {@link #onSaveInstanceState}.
*
- * @see #onSaveInstanceState
- * @see #restoreHierarchyState
- * @see #dispatchRestoreInstanceState
+ * @see #onSaveInstanceState()
+ * @see #restoreHierarchyState(android.util.SparseArray)
+ * @see #dispatchRestoreInstanceState(android.util.SparseArray)
*/
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= SAVE_STATE_CALLED;
@@ -8448,6 +9069,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
// Destroy any previous software drawing cache if needed
switch (mLayerType) {
+ case LAYER_TYPE_HARDWARE:
+ if (mHardwareLayer != null) {
+ mHardwareLayer.destroy();
+ mHardwareLayer = null;
+ }
+ // fall through - unaccelerated views may use software layer mechanism instead
case LAYER_TYPE_SOFTWARE:
if (mDrawingCache != null) {
mDrawingCache.recycle();
@@ -8459,12 +9086,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mUnscaledDrawingCache = null;
}
break;
- case LAYER_TYPE_HARDWARE:
- if (mHardwareLayer != null) {
- mHardwareLayer.destroy();
- mHardwareLayer = null;
- }
- break;
default:
break;
}
@@ -8555,7 +9176,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mLocalDirtyRect.setEmpty();
}
- Canvas currentCanvas = mAttachInfo.mHardwareCanvas;
+ HardwareCanvas currentCanvas = mAttachInfo.mHardwareCanvas;
final HardwareCanvas canvas = mHardwareLayer.start(currentCanvas);
mAttachInfo.mHardwareCanvas = canvas;
try {
@@ -9218,9 +9839,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
- * called. When implementing a view, implement {@link #onDraw} instead of
- * overriding this method. If you do need to override this method, call
- * the superclass version.
+ * called. When implementing a view, implement
+ * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
+ * If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@@ -9433,8 +10054,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* optimize the drawing of the fading edges. If you do return a non-zero color, the alpha
* should be set to 0xFF.
*
- * @see #setVerticalFadingEdgeEnabled
- * @see #setHorizontalFadingEdgeEnabled
+ * @see #setVerticalFadingEdgeEnabled(boolean)
+ * @see #setHorizontalFadingEdgeEnabled(boolean)
*
* @return The known solid color background for this view, or 0 if the color may vary
*/
@@ -9547,6 +10168,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * <p>Indicates whether or not this view's layout is right-to-left. This is resolved from
+ * layout attribute and/or the inherited value from the parent.</p>
+ *
+ * @return true if the layout is right-to-left.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public boolean isLayoutRtl() {
+ return (mPrivateFlags2 & RESOLVED_LAYOUT_RTL) == RESOLVED_LAYOUT_RTL;
+ }
+
+ /**
* Assign a size and position to a view and all of its
* descendants
*
@@ -9758,6 +10390,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
}
+ /**
+ * Check if a given Drawable is in RTL layout direction.
+ *
+ * @param who the recipient of the action
+ */
+ public boolean isLayoutRtl(Drawable who) {
+ return (who == mBGDrawable) && isLayoutRtl();
+ }
+
/**
* If your view subclass is displaying its own Drawable objects, it should
* override this function and return true for any Drawable it is
@@ -9774,8 +10415,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return boolean If true than the Drawable is being displayed in the
* view; else false and it is not allowed to animate.
*
- * @see #unscheduleDrawable
- * @see #drawableStateChanged
+ * @see #unscheduleDrawable(android.graphics.drawable.Drawable)
+ * @see #drawableStateChanged()
*/
protected boolean verifyDrawable(Drawable who) {
return who == mBGDrawable;
@@ -9788,7 +10429,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* <p>Be sure to call through to the superclass when overriding this
* function.
*
- * @see Drawable#setState
+ * @see Drawable#setState(int[])
*/
protected void drawableStateChanged() {
Drawable d = mBGDrawable;
@@ -9821,9 +10462,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return The current drawable state
*
- * @see Drawable#setState
- * @see #drawableStateChanged
- * @see #onCreateDrawableState
+ * @see Drawable#setState(int[])
+ * @see #drawableStateChanged()
+ * @see #onCreateDrawableState(int)
*/
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
@@ -9848,7 +10489,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return Returns an array holding the current {@link Drawable} state of
* the view.
*
- * @see #mergeDrawableStates
+ * @see #mergeDrawableStates(int[], int[])
*/
protected int[] onCreateDrawableState(int extraSpace) {
if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
@@ -9867,12 +10508,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
- if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested) {
+ if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
+ HardwareRenderer.isAvailable()) {
// This is set if HW acceleration is requested, even if the current
// process doesn't allow it. This is just to allow app preview
// windows to better match their app.
viewStateIndex |= VIEW_STATE_ACCELERATED;
}
+ if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
+
+ final int privateFlags2 = mPrivateFlags2;
+ if ((privateFlags2 & DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
+ if ((privateFlags2 & DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
drawableState = VIEW_STATE_SETS[viewStateIndex];
@@ -9906,10 +10553,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Merge your own state values in <var>additionalState</var> into the base
* state values <var>baseState</var> that were returned by
- * {@link #onCreateDrawableState}.
+ * {@link #onCreateDrawableState(int)}.
*
* @param baseState The base state values returned by
- * {@link #onCreateDrawableState}, which will be modified to also hold your
+ * {@link #onCreateDrawableState(int)}, which will be modified to also hold your
* own additional state values.
*
* @param additionalState The additional state values you would like
@@ -9918,7 +10565,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return As a convenience, the <var>baseState</var> array you originally
* passed into the function is returned.
*
- * @see #onCreateDrawableState
+ * @see #onCreateDrawableState(int)
*/
protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) {
final int N = baseState.length;
@@ -10348,9 +10995,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
viewParent = view.mParent;
}
- if (viewParent instanceof ViewRoot) {
+ if (viewParent instanceof ViewAncestor) {
// *cough*
- final ViewRoot vr = (ViewRoot)viewParent;
+ final ViewAncestor vr = (ViewAncestor)viewParent;
location[1] -= vr.mCurScrollY;
}
}
@@ -10437,8 +11084,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* number.
*
* @see #NO_ID
- * @see #getId
- * @see #findViewById
+ * @see #getId()
+ * @see #findViewById(int)
*
* @param id a number used to identify the view
*
@@ -10477,8 +11124,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return a positive integer used to identify the view or {@link #NO_ID}
* if the view has no ID
*
- * @see #setId
- * @see #findViewById
+ * @see #setId(int)
+ * @see #findViewById(int)
* @attr ref android.R.styleable#View_id
*/
@ViewDebug.CapturedViewProperty
@@ -11160,7 +11807,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* therefore all View objects remove themselves from the global transparent
* region (passed as a parameter to this function).
*
- * @param region The transparent region for this ViewRoot (window).
+ * @param region The transparent region for this ViewAncestor (window).
*
* @return Returns true if the effective visibility of the view at this
* point is opaque, regardless of the transparent region; returns false
@@ -11347,6 +11994,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return The View object associate with this builder object.
*/
+ @SuppressWarnings({"JavadocReference"})
final public View getView() {
return mView.get();
}
@@ -11472,7 +12120,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
surface.unlockCanvasAndPost(canvas);
}
- final ViewRoot root = getViewRoot();
+ final ViewAncestor root = getViewAncestor();
// Cache the local state object for delivery with DragEvents
root.setLocalDragState(myLocalState);
@@ -11546,6 +12194,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return onDragEvent(event);
}
+ boolean canAcceptDrag() {
+ return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
+ }
+
/**
* This needs to be a better API (NOT ON VIEW) before it is exposed. If
* it is ever exposed at all.
@@ -11556,7 +12208,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Given a Drawable whose bounds have been set to draw into this view,
- * update a Region being computed for {@link #gatherTransparentRegion} so
+ * update a Region being computed for
+ * {@link #gatherTransparentRegion(android.graphics.Region)} so
* that any non-transparent parts of the Drawable are removed from the
* given transparent region.
*
@@ -11603,15 +12256,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
}
- private void postCheckForLongClick(int delayOffset) {
- mHasPerformedLongPress = false;
+ private void checkForLongClick(int delayOffset) {
+ if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+ mHasPerformedLongPress = false;
- if (mPendingCheckForLongPress == null) {
- mPendingCheckForLongPress = new CheckForLongPress();
+ if (mPendingCheckForLongPress == null) {
+ mPendingCheckForLongPress = new CheckForLongPress();
+ }
+ mPendingCheckForLongPress.rememberWindowAttachCount();
+ postDelayed(mPendingCheckForLongPress,
+ ViewConfiguration.getLongPressTimeout() - delayOffset);
}
- mPendingCheckForLongPress.rememberWindowAttachCount();
- postDelayed(mPendingCheckForLongPress,
- ViewConfiguration.getLongPressTimeout() - delayOffset);
}
/**
@@ -11783,6 +12438,169 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return getVerticalScrollFactor();
}
+ //
+ // Properties
+ //
+ /**
+ * A Property wrapper around the <code>alpha</code> functionality handled by the
+ * {@link View#setAlpha(float)} and {@link View#getAlpha()} methods.
+ */
+ static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setAlpha(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getAlpha();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>translationX</code> functionality handled by the
+ * {@link View#setTranslationX(float)} and {@link View#getTranslationX()} methods.
+ */
+ public static Property<View, Float> TRANSLATION_X = new FloatProperty<View>("translationX") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setTranslationX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getTranslationX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>translationY</code> functionality handled by the
+ * {@link View#setTranslationY(float)} and {@link View#getTranslationY()} methods.
+ */
+ public static Property<View, Float> TRANSLATION_Y = new FloatProperty<View>("translationY") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setTranslationY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getTranslationY();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>x</code> functionality handled by the
+ * {@link View#setX(float)} and {@link View#getX()} methods.
+ */
+ public static Property<View, Float> X = new FloatProperty<View>("x") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>y</code> functionality handled by the
+ * {@link View#setY(float)} and {@link View#getY()} methods.
+ */
+ public static Property<View, Float> Y = new FloatProperty<View>("y") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getY();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>rotation</code> functionality handled by the
+ * {@link View#setRotation(float)} and {@link View#getRotation()} methods.
+ */
+ public static Property<View, Float> ROTATION = new FloatProperty<View>("rotation") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setRotation(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getRotation();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>rotationX</code> functionality handled by the
+ * {@link View#setRotationX(float)} and {@link View#getRotationX()} methods.
+ */
+ public static Property<View, Float> ROTATION_X = new FloatProperty<View>("rotationX") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setRotationX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getRotationX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>rotationY</code> functionality handled by the
+ * {@link View#setRotationY(float)} and {@link View#getRotationY()} methods.
+ */
+ public static Property<View, Float> ROTATION_Y = new FloatProperty<View>("rotationY") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setRotationY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getRotationY();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>scaleX</code> functionality handled by the
+ * {@link View#setScaleX(float)} and {@link View#getScaleX()} methods.
+ */
+ public static Property<View, Float> SCALE_X = new FloatProperty<View>("scaleX") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setScaleX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getScaleX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>scaleY</code> functionality handled by the
+ * {@link View#setScaleY(float)} and {@link View#getScaleY()} methods.
+ */
+ public static Property<View, Float> SCALE_Y = new FloatProperty<View>("scaleY") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setScaleY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getScaleY();
+ }
+ };
+
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
@@ -11923,9 +12741,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mPrivateFlags &= ~PREPRESSED;
mPrivateFlags |= PRESSED;
refreshDrawableState();
- if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
- postCheckForLongClick(ViewConfiguration.getTapTimeout());
- }
+ checkForLongClick(ViewConfiguration.getTapTimeout());
}
}
@@ -12090,12 +12906,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Interface definition for a callback to be invoked when the status bar changes
* visibility.
*
- * @see #setOnSystemUiVisibilityChangeListener
+ * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener)
*/
public interface OnSystemUiVisibilityChangeListener {
/**
* Called when the status bar changes visibility because of a call to
- * {@link #setSystemUiVisibility}.
+ * {@link View#setSystemUiVisibility(int)}.
*
* @param visibility {@link #STATUS_BAR_VISIBLE} or {@link #STATUS_BAR_HIDDEN}.
*/
@@ -12196,6 +13012,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
);
private InvalidateInfo mNext;
+ private boolean mIsPooled;
View target;
@@ -12219,6 +13036,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
void release() {
sPool.release(this);
}
+
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
}
final IWindowSession mSession;
@@ -12229,7 +13054,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final Callbacks mRootCallbacks;
- Canvas mHardwareCanvas;
+ HardwareCanvas mHardwareCanvas;
/**
* The top view of the hierarchy.
@@ -12254,7 +13079,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
boolean mScalingRequired;
/**
- * If set, ViewRoot doesn't use its lame animation for when the window resizes.
+ * If set, ViewAncestor doesn't use its lame animation for when the window resizes.
*/
boolean mTurnOffWindowResizeAnim;
@@ -12333,7 +13158,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
boolean mInTouchMode;
/**
- * Indicates that ViewRoot should trigger a global layout change
+ * Indicates that ViewAncestor should trigger a global layout change
* the next time it performs a traversal
*/
boolean mRecomputeGlobalAttributes;
@@ -12395,7 +13220,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
Canvas mCanvas;
/**
- * A Handler supplied by a view's {@link android.view.ViewRoot}. This
+ * A Handler supplied by a view's {@link android.view.ViewAncestor}. This
* handler can be used to pump events in the UI events queue.
*/
final Handler mHandler;
@@ -12429,6 +13254,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
/**
+ * The id of the window for accessibility purposes.
+ */
+ int mAccessibilityWindowId = View.NO_ID;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewAncestor.java
index 1440a81..17d7454 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -17,6 +17,7 @@
package android.view;
import android.Manifest;
+import android.animation.LayoutTransition;
import android.app.ActivityManagerNative;
import android.content.ClipDescription;
import android.content.ComponentCallbacks;
@@ -25,7 +26,6 @@ import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -45,26 +45,34 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pool;
+import android.util.Poolable;
+import android.util.PoolableManager;
+import android.util.Pools;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
+
import com.android.internal.policy.PolicyManager;
+import com.android.internal.util.Predicate;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
@@ -74,6 +82,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -83,11 +92,10 @@ import java.util.ArrayList;
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
-public final class ViewRoot extends Handler implements ViewParent,
+public final class ViewAncestor extends Handler implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
- private static final String TAG = "ViewRoot";
+ private static final String TAG = "ViewAncestor";
private static final boolean DBG = false;
- private static final boolean SHOW_FPS = false;
private static final boolean LOCAL_LOGV = false;
/** @noinspection PointlessBooleanExpression*/
private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
@@ -100,6 +108,12 @@ public final class ViewRoot extends Handler implements ViewParent,
private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
private static final boolean WATCH_POINTER = false;
+ /**
+ * Set this system property to true to force the view hierarchy to render
+ * at 60 Hz. This can be used to measure the potential framerate.
+ */
+ private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering";
+
private static final boolean MEASURE_LATENCY = false;
private static LatencyTimer lt;
@@ -121,8 +135,6 @@ public final class ViewRoot extends Handler implements ViewParent,
static final ArrayList<ComponentCallbacks> sConfigCallbacks
= new ArrayList<ComponentCallbacks>();
-
- private static int sDrawTime;
long mLastTrackballTime = 0;
final TrackballAxis mTrackballAxisX = new TrackballAxis();
@@ -188,6 +200,8 @@ public final class ViewRoot extends Handler implements ViewParent,
final Rect mVisRect; // used to retrieve visible rect of focused view.
boolean mTraversalScheduled;
+ long mLastTraversalFinishedTimeNanos;
+ long mLastDrawDurationNanos;
boolean mWillDrawSoon;
boolean mLayoutRequested;
boolean mFirst;
@@ -220,7 +234,7 @@ public final class ViewRoot extends Handler implements ViewParent,
final Configuration mLastConfiguration = new Configuration();
final Configuration mPendingConfiguration = new Configuration();
-
+
class ResizedInfo {
Rect coveredInsets;
Rect visibleInsets;
@@ -233,10 +247,11 @@ public final class ViewRoot extends Handler implements ViewParent,
int mScrollY;
int mCurScrollY;
Scroller mScroller;
- Bitmap mResizeBitmap;
- long mResizeBitmapStartTime;
- int mResizeBitmapDuration;
+ HardwareLayer mResizeBuffer;
+ long mResizeBufferStartTime;
+ int mResizeBufferDuration;
static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
+ private ArrayList<LayoutTransition> mPendingTransitions;
final ViewConfiguration mViewConfiguration;
@@ -246,14 +261,31 @@ public final class ViewRoot extends Handler implements ViewParent,
volatile Object mLocalDragState;
final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF();
+
+ private boolean mProfileRendering;
+ private Thread mRenderProfiler;
+ private volatile boolean mRenderProfilingEnabled;
/**
* see {@link #playSoundEffect(int)}
*/
AudioManager mAudioManager;
+ final AccessibilityManager mAccessibilityManager;
+
+ AccessibilityInteractionController mAccessibilityInteractionContrtoller;
+
+ AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager;
+
private final int mDensity;
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
public static IWindowSession getWindowSession(Looper mainLooper) {
synchronized (mStaticInit) {
if (!mInitialized) {
@@ -269,7 +301,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
- public ViewRoot(Context context) {
+ public ViewAncestor(Context context) {
super();
if (MEASURE_LATENCY) {
@@ -282,7 +314,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// done here instead of in the static block because Zygote does not
// allow the spawning of threads.
getWindowSession(context.getMainLooper());
-
+
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
@@ -299,10 +331,17 @@ public final class ViewRoot extends Handler implements ViewParent,
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mAccessibilityInteractionConnectionManager =
+ new AccessibilityInteractionConnectionManager();
+ mAccessibilityManager.addAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager);
mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
+ mProfileRendering = Boolean.parseBoolean(
+ SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false"));
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -487,10 +526,14 @@ public final class ViewRoot extends Handler implements ViewParent,
InputQueue.registerInputChannel(mInputChannel, mInputHandler,
Looper.myQueue());
}
-
+
view.assignParent(this);
mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
+
+ if (mAccessibilityManager.isEnabled()) {
+ mAccessibilityInteractionConnectionManager.ensureConnection();
+ }
}
}
}
@@ -503,9 +546,14 @@ public final class ViewRoot extends Handler implements ViewParent,
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
- if (attrs != null && hardwareAccelerated) {
+ if (hardwareAccelerated) {
+ if (!HardwareRenderer.isAvailable()) {
+ mAttachInfo.mHardwareAccelerationRequested = true;
+ return;
+ }
+
// Only enable hardware acceleration if we are not in the system process
- // The window manager creates ViewRoots to display animated preview windows
+ // The window manager creates ViewAncestors to display animated preview windows
// of launching apps and we don't want those to be hardware accelerated
final boolean systemHwAccelerated =
@@ -526,8 +574,6 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested
= mAttachInfo.mHardwareRenderer != null;
- } else if (HardwareRenderer.isAvailable()) {
- mAttachInfo.mHardwareAccelerationRequested = true;
}
}
}
@@ -663,6 +709,15 @@ public final class ViewRoot extends Handler implements ViewParent,
public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
+
+ //noinspection ConstantConditions
+ if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) {
+ final long now = System.nanoTime();
+ Log.d(TAG, "Latency: Scheduled traversal, it has been "
+ + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f)
+ + "ms since the last traversal finished.");
+ }
+
sendEmptyMessage(DO_TRAVERSAL);
}
}
@@ -678,10 +733,32 @@ public final class ViewRoot extends Handler implements ViewParent,
return mAppVisible ? mView.getVisibility() : View.GONE;
}
- void disposeResizeBitmap() {
- if (mResizeBitmap != null) {
- mResizeBitmap.recycle();
- mResizeBitmap = null;
+ void disposeResizeBuffer() {
+ if (mResizeBuffer != null) {
+ mResizeBuffer.destroy();
+ mResizeBuffer = null;
+ }
+ }
+
+ /**
+ * Add LayoutTransition to the list of transitions to be started in the next traversal.
+ * This list will be cleared after the transitions on the list are start()'ed. These
+ * transitionsa re added by LayoutTransition itself when it sets up animations. The setup
+ * happens during the layout phase of traversal, which we want to complete before any of the
+ * animations are started (because those animations may side-effect properties that layout
+ * depends upon, like the bounding rectangles of the affected views). So we add the transition
+ * to the list and it is started just prior to starting the drawing phase of traversal.
+ *
+ * @param transition The LayoutTransition to be started on the next traversal.
+ *
+ * @hide
+ */
+ public void requestTransitionStart(LayoutTransition transition) {
+ if (mPendingTransitions == null || !mPendingTransitions.contains(transition)) {
+ if (mPendingTransitions == null) {
+ mPendingTransitions = new ArrayList<LayoutTransition>();
+ }
+ mPendingTransitions.add(transition);
}
}
@@ -823,15 +900,25 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mHardwareRenderer.isEnabled() &&
lp != null && !PixelFormat.formatHasAlpha(lp.format)) {
- disposeResizeBitmap();
+ disposeResizeBuffer();
boolean completed = false;
+ HardwareCanvas canvas = null;
try {
- mResizeBitmap = Bitmap.createBitmap(mWidth, mHeight,
- Bitmap.Config.ARGB_8888);
- mResizeBitmap.setHasAlpha(false);
- Canvas canvas = new Canvas(mResizeBitmap);
+ if (mResizeBuffer == null) {
+ mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
+ mWidth, mHeight, false);
+ } else if (mResizeBuffer.getWidth() != mWidth ||
+ mResizeBuffer.getHeight() != mHeight) {
+ mResizeBuffer.resize(mWidth, mHeight);
+ }
+ canvas = mResizeBuffer.start(mAttachInfo.mHardwareCanvas);
+ canvas.setViewport(mWidth, mHeight);
+ canvas.onPreDraw(null);
+ final int restoreCount = canvas.save();
+
canvas.drawColor(0xff000000, PorterDuff.Mode.SRC);
+
int yoff;
final boolean scrolling = mScroller != null
&& mScroller.computeScrollOffset();
@@ -841,22 +928,32 @@ public final class ViewRoot extends Handler implements ViewParent,
} else {
yoff = mScrollY;
}
+
canvas.translate(0, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
- canvas.setScreenDensity(mAttachInfo.mScalingRequired
- ? DisplayMetrics.DENSITY_DEVICE : 0);
+
mView.draw(canvas);
- mResizeBitmapStartTime = SystemClock.uptimeMillis();
- mResizeBitmapDuration = mView.getResources().getInteger(
+
+ mResizeBufferStartTime = SystemClock.uptimeMillis();
+ mResizeBufferDuration = mView.getResources().getInteger(
com.android.internal.R.integer.config_mediumAnimTime);
completed = true;
+
+ canvas.restoreToCount(restoreCount);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Not enough memory for content change anim buffer", e);
} finally {
- if (!completed) {
- mResizeBitmap = null;
+ if (canvas != null) {
+ canvas.onPostDraw();
+ }
+ if (mResizeBuffer != null) {
+ mResizeBuffer.end(mAttachInfo.mHardwareCanvas);
+ if (!completed) {
+ mResizeBuffer.destroy();
+ mResizeBuffer = null;
+ }
}
}
}
@@ -1125,7 +1222,7 @@ public final class ViewRoot extends Handler implements ViewParent,
if (mScroller != null) {
mScroller.abortAnimation();
}
- disposeResizeBitmap();
+ disposeResizeBuffer();
} else if (surfaceGenerationId != mSurface.getGenerationId() &&
mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
fullRedrawNeeded = true;
@@ -1282,7 +1379,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
- if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
+ if (false && ViewDebug.consistencyCheckEnabled) {
if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) {
throw new IllegalStateException("The view hierarchy is an inconsistent state,"
+ "please refer to the logs with the tag "
@@ -1407,9 +1504,25 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
if (!cancelDraw && !newSurface) {
+ if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ mPendingTransitions.get(i).startChangingAnimations();
+ }
+ mPendingTransitions.clear();
+ }
mFullRedrawNeeded = false;
+
+ final long drawStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ drawStartTime = System.nanoTime();
+ }
+
draw(fullRedrawNeeded);
+ if (ViewDebug.DEBUG_LATENCY) {
+ mLastDrawDurationNanos = System.nanoTime() - drawStartTime;
+ }
+
if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0
|| mReportNextDraw) {
if (LOCAL_LOGV) {
@@ -1496,15 +1609,62 @@ public final class ViewRoot extends Handler implements ViewParent,
int mResizeAlpha;
final Paint mResizePaint = new Paint();
- public void onHardwarePreDraw(Canvas canvas) {
+ public void onHardwarePreDraw(HardwareCanvas canvas) {
canvas.translate(0, -mHardwareYOffset);
}
- public void onHardwarePostDraw(Canvas canvas) {
- if (mResizeBitmap != null) {
- canvas.translate(0, mHardwareYOffset);
+ public void onHardwarePostDraw(HardwareCanvas canvas) {
+ if (mResizeBuffer != null) {
mResizePaint.setAlpha(mResizeAlpha);
- canvas.drawBitmap(mResizeBitmap, 0, 0, mResizePaint);
+ canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void outputDisplayList(View view) {
+ if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) {
+ DisplayList displayList = view.getDisplayList();
+ if (displayList != null) {
+ mAttachInfo.mHardwareCanvas.outputDisplayList(displayList);
+ }
+ }
+ }
+
+ /**
+ * @see #PROPERTY_PROFILE_RENDERING
+ */
+ 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");
+ }
+ }
+ }
+ }, "Rendering Profiler");
+ mRenderProfiler.start();
+ } else {
+ mRenderProfiler.interrupt();
+ mRenderProfiler = null;
+ }
}
}
@@ -1523,7 +1683,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
-
+
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
@@ -1546,15 +1706,15 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
- if (mResizeBitmap != null) {
- long deltaTime = SystemClock.uptimeMillis() - mResizeBitmapStartTime;
- if (deltaTime < mResizeBitmapDuration) {
- float amt = deltaTime/(float)mResizeBitmapDuration;
+ if (mResizeBuffer != null) {
+ long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
+ if (deltaTime < mResizeBufferDuration) {
+ float amt = deltaTime/(float) mResizeBufferDuration;
amt = mResizeInterpolator.getInterpolation(amt);
animating = true;
resizeAlpha = 255 - (int)(amt*255);
} else {
- disposeResizeBitmap();
+ disposeResizeBuffer();
}
}
@@ -1566,7 +1726,7 @@ public final class ViewRoot extends Handler implements ViewParent,
if (mScroller != null) {
mScroller.abortAnimation();
}
- disposeResizeBitmap();
+ disposeResizeBuffer();
}
return;
}
@@ -1620,8 +1780,20 @@ public final class ViewRoot extends Handler implements ViewParent,
int right = dirty.right;
int bottom = dirty.bottom;
+ final long lockCanvasStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ lockCanvasStartTime = System.nanoTime();
+ }
+
canvas = surface.lockCanvas(dirty);
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(TAG, "Latency: Spent "
+ + ((now - lockCanvasStartTime) * 0.000001f)
+ + "ms waiting for surface.lockCanvas()");
+ }
+
if (left != dirty.left || top != dirty.top || right != dirty.right ||
bottom != dirty.bottom) {
mAttachInfo.mIgnoreDirtyState = true;
@@ -1698,18 +1870,10 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mIgnoreDirtyState = false;
}
- if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
+ if (false && ViewDebug.consistencyCheckEnabled) {
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
- if (SHOW_FPS || ViewDebug.DEBUG_SHOW_FPS) {
- int now = (int)SystemClock.elapsedRealtime();
- if (sDrawTime != 0) {
- nativeShowFPS(canvas, now - sDrawTime);
- }
- sDrawTime = now;
- }
-
if (ViewDebug.DEBUG_PROFILE_DRAWING) {
EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
}
@@ -1838,7 +2002,7 @@ public final class ViewRoot extends Handler implements ViewParent,
if (scrollY != mScrollY) {
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
+ mScrollY + " , new=" + scrollY);
- if (!immediate && mResizeBitmap == null) {
+ if (!immediate && mResizeBuffer == null) {
if (mScroller == null) {
mScroller = new Scroller(mView.getContext());
}
@@ -1884,20 +2048,22 @@ public final class ViewRoot extends Handler implements ViewParent,
public void focusableViewAvailable(View v) {
checkThread();
- if (mView != null && !mView.hasFocus()) {
- v.requestFocus();
- } else {
- // 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()
+ if (mView != null) {
+ if (!mView.hasFocus()) {
v.requestFocus();
+ } else {
+ // 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();
+ }
}
}
}
@@ -1917,6 +2083,10 @@ public final class ViewRoot extends Handler implements ViewParent,
mView.dispatchDetachedFromWindow();
}
+ mAccessibilityInteractionConnectionManager.ensureNoConnection();
+ mAccessibilityManager.removeAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager);
+
mView = null;
mAttachInfo.mRootView = null;
mAttachInfo.mSurface = null;
@@ -1933,7 +2103,6 @@ public final class ViewRoot extends Handler implements ViewParent,
InputQueue.unregisterInputChannel(mInputChannel);
}
}
-
try {
sWindowSession.remove(mWindow);
} catch (RemoteException e) {
@@ -1968,9 +2137,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// At this point the resources have been updated to
// have the most recent config, whatever that is. Use
// the on in them which may be newer.
- if (mView != null) {
- config = mView.getResources().getConfiguration();
- }
+ config = mView.getResources().getConfiguration();
if (force || mLastConfiguration.diff(config) != 0) {
mLastConfiguration.setTo(config);
mView.dispatchConfigurationChanged(config);
@@ -2021,6 +2188,10 @@ public final class ViewRoot extends Handler implements ViewParent,
public final static int DISPATCH_SYSTEM_UI_VISIBILITY = 1017;
public final static int DISPATCH_GENERIC_MOTION = 1018;
public final static int UPDATE_CONFIGURATION = 1019;
+ public final static int DO_PERFORM_ACCESSIBILITY_ACTION = 1020;
+ public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021;
+ public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022;
+ public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1023;
@Override
public void handleMessage(Message msg) {
@@ -2035,11 +2206,27 @@ public final class ViewRoot extends Handler implements ViewParent,
break;
case DO_TRAVERSAL:
if (mProfile) {
- Debug.startMethodTracing("ViewRoot");
+ Debug.startMethodTracing("ViewAncestor");
+ }
+
+ final long traversalStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ traversalStartTime = System.nanoTime();
+ mLastDrawDurationNanos = 0;
}
performTraversals();
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(TAG, "Latency: Spent "
+ + ((now - traversalStartTime) * 0.000001f)
+ + "ms in performTraversals(), with "
+ + (mLastDrawDurationNanos * 0.000001f)
+ + "ms of that time in draw()");
+ mLastTraversalFinishedTimeNanos = now;
+ }
+
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
@@ -2102,6 +2289,9 @@ public final class ViewRoot extends Handler implements ViewParent,
if (mAdded) {
boolean hasWindowFocus = msg.arg1 != 0;
mAttachInfo.mHasWindowFocus = hasWindowFocus;
+
+ profileRendering(hasWindowFocus);
+
if (hasWindowFocus) {
boolean inTouchMode = msg.arg2 != 0;
ensureTouchModeLocally(inTouchMode);
@@ -2174,6 +2364,7 @@ public final class ViewRoot extends Handler implements ViewParent,
if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
// The IME is trying to say this event is from the
// system! Bad bad bad!
+ //noinspection UnusedAssignment
event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
}
deliverKeyEventPostIme((KeyEvent)msg.obj, false);
@@ -2211,28 +2402,95 @@ public final class ViewRoot extends Handler implements ViewParent,
}
updateConfiguration(config, false);
} break;
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByAccessibilityIdUiThread(msg);
+ }
+ } break;
+ case DO_PERFORM_ACCESSIBILITY_ACTION: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .perfromAccessibilityActionUiThread(msg);
+ }
+ } break;
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByViewIdUiThread(msg);
+ }
+ } break;
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByViewTextUiThread(msg);
+ }
+ } break;
}
}
-
+
private void startInputEvent(InputQueue.FinishedCallback finishedCallback) {
if (mFinishedCallback != null) {
Slog.w(TAG, "Received a new input event from the input queue but there is "
+ "already an unfinished input event in progress.");
}
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventReceiveTimeNanos = System.nanoTime();
+ mInputEventDeliverTimeNanos = 0;
+ mInputEventDeliverPostImeTimeNanos = 0;
+ }
+
mFinishedCallback = finishedCallback;
}
- private void finishInputEvent(boolean handled) {
+ private void finishInputEvent(InputEvent event, boolean handled) {
if (LOCAL_LOGV) Log.v(TAG, "Telling window manager input event is finished");
- if (mFinishedCallback != null) {
- mFinishedCallback.finished(handled);
- mFinishedCallback = null;
- } else {
+ if (mFinishedCallback == null) {
Slog.w(TAG, "Attempted to tell the input queue that the current input event "
+ "is finished but there is no input event actually in progress.");
+ return;
}
+
+ if (ViewDebug.DEBUG_LATENCY) {
+ final long now = System.nanoTime();
+ final long eventTime = event.getEventTimeNano();
+ final StringBuilder msg = new StringBuilder();
+ msg.append("Latency: Spent ");
+ msg.append((now - mInputEventReceiveTimeNanos) * 0.000001f);
+ msg.append("ms processing ");
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ msg.append("key event, action=");
+ msg.append(KeyEvent.actionToString(keyEvent.getAction()));
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ msg.append("motion event, action=");
+ msg.append(MotionEvent.actionToString(motionEvent.getAction()));
+ msg.append(", historySize=");
+ msg.append(motionEvent.getHistorySize());
+ }
+ msg.append(", handled=");
+ msg.append(handled);
+ msg.append(", received at +");
+ msg.append((mInputEventReceiveTimeNanos - eventTime) * 0.000001f);
+ if (mInputEventDeliverTimeNanos != 0) {
+ msg.append("ms, delivered at +");
+ msg.append((mInputEventDeliverTimeNanos - eventTime) * 0.000001f);
+ }
+ if (mInputEventDeliverPostImeTimeNanos != 0) {
+ msg.append("ms, delivered post IME at +");
+ msg.append((mInputEventDeliverPostImeTimeNanos - eventTime) * 0.000001f);
+ }
+ msg.append("ms, finished at +");
+ msg.append((now - eventTime) * 0.000001f);
+ msg.append("ms.");
+ Log.d(TAG, msg.toString());
+ }
+
+ mFinishedCallback.finished(handled);
+ mFinishedCallback = null;
}
/**
@@ -2357,6 +2615,18 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverPointerEvent(MotionEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ if (event.isTouchEvent()) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+ } else {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ }
+ }
+
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishMotionEvent(event, sendDone, false);
@@ -2373,9 +2643,6 @@ public final class ViewRoot extends Handler implements ViewParent,
if (isDown) {
ensureTouchMode(true);
}
- if(Config.LOGV) {
- captureMotionLog("captureDispatchPointer", event);
- }
// Offset the scroll position.
if (mCurScrollY != 0) {
@@ -2451,8 +2718,9 @@ public final class ViewRoot extends Handler implements ViewParent,
private void finishMotionEvent(MotionEvent event, boolean sendDone, boolean handled) {
event.recycle();
if (sendDone) {
- finishInputEvent(handled);
+ finishInputEvent(event, handled);
}
+ //noinspection ConstantConditions
if (LOCAL_LOGV || WATCH_POINTER) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
Log.i(TAG, "Done dispatching!");
@@ -2461,8 +2729,16 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverTrackballEvent(MotionEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
+ }
+
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishMotionEvent(event, sendDone, false);
@@ -2591,6 +2867,14 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverGenericMotionEvent(MotionEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ }
+
final int source = event.getSource();
final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
@@ -2762,52 +3046,6 @@ public final class ViewRoot extends Handler implements ViewParent,
return false;
}
- /**
- * log motion events
- */
- private static void captureMotionLog(String subTag, MotionEvent ev) {
- //check dynamic switch
- if (ev == null ||
- SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
- return;
- }
-
- StringBuilder sb = new StringBuilder(subTag + ": ");
- sb.append(ev.getDownTime()).append(',');
- sb.append(ev.getEventTime()).append(',');
- sb.append(ev.getAction()).append(',');
- sb.append(ev.getX()).append(',');
- sb.append(ev.getY()).append(',');
- sb.append(ev.getPressure()).append(',');
- sb.append(ev.getSize()).append(',');
- sb.append(ev.getMetaState()).append(',');
- sb.append(ev.getXPrecision()).append(',');
- sb.append(ev.getYPrecision()).append(',');
- sb.append(ev.getDeviceId()).append(',');
- sb.append(ev.getEdgeFlags());
- Log.d(TAG, sb.toString());
- }
- /**
- * log motion events
- */
- private static void captureKeyLog(String subTag, KeyEvent ev) {
- //check dynamic switch
- if (ev == null ||
- SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
- return;
- }
- StringBuilder sb = new StringBuilder(subTag + ": ");
- sb.append(ev.getDownTime()).append(',');
- sb.append(ev.getEventTime()).append(',');
- sb.append(ev.getAction()).append(',');
- sb.append(ev.getKeyCode()).append(',');
- sb.append(ev.getRepeatCount()).append(',');
- sb.append(ev.getMetaState()).append(',');
- sb.append(ev.getDeviceId()).append(',');
- sb.append(ev.getScanCode());
- Log.d(TAG, sb.toString());
- }
-
int enqueuePendingEvent(Object event, boolean sendDone) {
int seq = mPendingEventSeq+1;
if (seq < 0) seq = 0;
@@ -2826,6 +3064,14 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 0);
+ }
+
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishKeyEvent(event, sendDone, false);
@@ -2872,6 +3118,10 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverPostImeTimeNanos = System.nanoTime();
+ }
+
// If the view went away, then the event will not be handled.
if (mView == null || !mAdded) {
finishKeyEvent(event, sendDone, false);
@@ -2884,10 +3134,6 @@ public final class ViewRoot extends Handler implements ViewParent,
return;
}
- if (Config.LOGV) {
- captureKeyLog("captureDispatchKeyEvent", event);
- }
-
// Make sure the fallback event policy sees all keys that will be delivered to the
// view hierarchy.
mFallbackEventHandler.preDispatchKeyEvent(event);
@@ -2985,7 +3231,7 @@ public final class ViewRoot extends Handler implements ViewParent,
private void finishKeyEvent(KeyEvent event, boolean sendDone, boolean handled) {
if (sendDone) {
- finishInputEvent(handled);
+ finishInputEvent(event, handled);
}
}
@@ -3102,6 +3348,17 @@ public final class ViewRoot extends Handler implements ViewParent,
return mAudioManager;
}
+ public AccessibilityInteractionController getAccessibilityInteractionController() {
+ if (mView == null) {
+ throw new IllegalStateException("getAccessibilityInteractionController"
+ + " called when there is no mView");
+ }
+ if (mAccessibilityInteractionContrtoller == null) {
+ mAccessibilityInteractionContrtoller = new AccessibilityInteractionController();
+ }
+ return mAccessibilityInteractionContrtoller;
+ }
+
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
@@ -3281,6 +3538,9 @@ public final class ViewRoot extends Handler implements ViewParent,
sendMessage(msg);
}
+ private long mInputEventReceiveTimeNanos;
+ private long mInputEventDeliverTimeNanos;
+ private long mInputEventDeliverPostImeTimeNanos;
private InputQueue.FinishedCallback mFinishedCallback;
private final InputHandler mInputHandler = new InputHandler() {
@@ -3322,10 +3582,6 @@ public final class ViewRoot extends Handler implements ViewParent,
sendMessageAtTime(msg, event.getEventTime());
}
- public void dispatchMotion(MotionEvent event) {
- dispatchMotion(event, false);
- }
-
private void dispatchMotion(MotionEvent event, boolean sendDone) {
int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -3337,10 +3593,6 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
- public void dispatchPointer(MotionEvent event) {
- dispatchPointer(event, false);
- }
-
private void dispatchPointer(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
@@ -3348,10 +3600,6 @@ public final class ViewRoot extends Handler implements ViewParent,
sendMessageAtTime(msg, event.getEventTime());
}
- public void dispatchTrackball(MotionEvent event) {
- dispatchTrackball(event, false);
- }
-
private void dispatchTrackball(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_TRACKBALL);
msg.obj = event;
@@ -3413,7 +3661,7 @@ public final class ViewRoot extends Handler implements ViewParent,
* send an {@link AccessibilityEvent} to announce that.
*/
private void sendAccessibilityEvents() {
- if (!AccessibilityManager.getInstance(mView.getContext()).isEnabled()) {
+ if (!mAccessibilityManager.isEnabled()) {
return;
}
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -3437,6 +3685,14 @@ public final class ViewRoot extends Handler implements ViewParent,
public void childDrawableStateChanged(View child) {
}
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (mView == null) {
+ return false;
+ }
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ return true;
+ }
+
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
@@ -3445,7 +3701,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- // ViewRoot never intercepts touch event, so this can be a no-op
+ // ViewAncestor never intercepts touch event, so this can be a no-op
}
public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
@@ -3494,57 +3750,58 @@ public final class ViewRoot extends Handler implements ViewParent,
}
static class InputMethodCallback extends IInputMethodCallback.Stub {
- private WeakReference<ViewRoot> mViewRoot;
+ private WeakReference<ViewAncestor> mViewAncestor;
- public InputMethodCallback(ViewRoot viewRoot) {
- mViewRoot = new WeakReference<ViewRoot>(viewRoot);
+ public InputMethodCallback(ViewAncestor viewAncestor) {
+ mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor);
}
public void finishedEvent(int seq, boolean handled) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchFinishedEvent(seq, handled);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchFinishedEvent(seq, handled);
}
}
- public void sessionCreated(IInputMethodSession session) throws RemoteException {
+ public void sessionCreated(IInputMethodSession session) {
// Stub -- not for use in the client.
}
}
static class W extends IWindow.Stub {
- private final WeakReference<ViewRoot> mViewRoot;
+ private final WeakReference<ViewAncestor> mViewAncestor;
- W(ViewRoot viewRoot) {
- mViewRoot = new WeakReference<ViewRoot>(viewRoot);
+ W(ViewAncestor viewAncestor) {
+ mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor);
}
public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets,
boolean reportDraw, Configuration newConfig) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw, newConfig);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw,
+ newConfig);
}
}
public void dispatchAppVisibility(boolean visible) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchAppVisibility(visible);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchAppVisibility(visible);
}
}
public void dispatchGetNewSurface() {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchGetNewSurface();
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchGetNewSurface();
}
}
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.windowFocusChanged(hasFocus, inTouchMode);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
}
}
@@ -3562,9 +3819,9 @@ public final class ViewRoot extends Handler implements ViewParent,
}
public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- final View view = viewRoot.mView;
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ final View view = viewAncestor.mView;
if (view != null) {
if (checkCallingPermission(Manifest.permission.DUMP) !=
PackageManager.PERMISSION_GRANTED) {
@@ -3593,9 +3850,9 @@ public final class ViewRoot extends Handler implements ViewParent,
}
public void closeSystemDialogs(String reason) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchCloseSystemDialogs(reason);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchCloseSystemDialogs(reason);
}
}
@@ -3608,7 +3865,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
-
+
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
@@ -3621,17 +3878,16 @@ public final class ViewRoot extends Handler implements ViewParent,
/* Drag/drop */
public void dispatchDragEvent(DragEvent event) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchDragEvent(event);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchDragEvent(event);
}
}
- @Override
public void dispatchSystemUiVisibilityChanged(int visibility) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchSystemUiVisibilityChanged(visibility);
+ final ViewAncestor viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchSystemUiVisibilityChanged(visibility);
}
}
}
@@ -3941,5 +4197,384 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
- private static native void nativeShowFPS(Canvas canvas, int durationMillis);
+ /**
+ * Class for managing the accessibility interaction connection
+ * based on the global accessibility state.
+ */
+ final class AccessibilityInteractionConnectionManager
+ implements AccessibilityStateChangeListener {
+ public void onAccessibilityStateChanged(boolean enabled) {
+ if (enabled) {
+ ensureConnection();
+ } else {
+ ensureNoConnection();
+ }
+ }
+
+ public void ensureConnection() {
+ final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
+ if (!registered) {
+ mAttachInfo.mAccessibilityWindowId =
+ mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
+ new AccessibilityInteractionConnection(ViewAncestor.this));
+ }
+ }
+
+ public void ensureNoConnection() {
+ final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
+ if (registered) {
+ mAttachInfo.mAccessibilityWindowId = View.NO_ID;
+ mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
+ }
+ }
+ }
+
+ /**
+ * This class is an interface this ViewAncestor provides to the
+ * AccessibilityManagerService to the latter can interact with
+ * the view hierarchy in this ViewAncestor.
+ */
+ final class AccessibilityInteractionConnection
+ extends IAccessibilityInteractionConnection.Stub {
+ private final WeakReference<ViewAncestor> mViewAncestor;
+
+ AccessibilityInteractionConnection(ViewAncestor viewAncestor) {
+ mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor);
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityId(int accessibilityId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ if (mViewAncestor.get() != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityId,
+ interactionId, callback);
+ }
+ }
+
+ public void performAccessibilityAction(int accessibilityId, int action,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ if (mViewAncestor.get() != null) {
+ getAccessibilityInteractionController()
+ .performAccessibilityActionClientThread(accessibilityId, action, interactionId,
+ callback);
+ }
+ }
+
+ public void findAccessibilityNodeInfoByViewId(int viewId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ if (mViewAncestor.get() != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback);
+ }
+ }
+
+ public void findAccessibilityNodeInfosByViewText(String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {
+ if (mViewAncestor.get() != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByViewTextClientThread(text, interactionId,
+ callback);
+ }
+ }
+ }
+
+ /**
+ * Class for managing accessibility interactions initiated from the system
+ * and targeting the view hierarchy. A *ClientThread method is to be
+ * called from the interaction connection this ViewAncestor gives the
+ * system to talk to it and a corresponding *UiThread method that is executed
+ * on the UI thread.
+ */
+ final class AccessibilityInteractionController {
+ private static final int POOL_SIZE = 5;
+
+ private FindByAccessibilitytIdPredicate mFindByAccessibilityIdPredicate =
+ new FindByAccessibilitytIdPredicate();
+
+ private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ new ArrayList<AccessibilityNodeInfo>();
+
+ // Reusable poolable arguments for interacting with the view hierarchy
+ // to fit more arguments than Message and to avoid sharing objects between
+ // two messages since several threads can send messages concurrently.
+ private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
+ new PoolableManager<SomeArgs>() {
+ public SomeArgs newInstance() {
+ return new SomeArgs();
+ }
+
+ public void onAcquired(SomeArgs info) {
+ /* do nothing */
+ }
+
+ public void onReleased(SomeArgs info) {
+ info.clear();
+ }
+ }, POOL_SIZE)
+ );
+
+ public class SomeArgs implements Poolable<SomeArgs> {
+ private SomeArgs mNext;
+ private boolean mIsPooled;
+
+ public Object arg1;
+ public Object arg2;
+ public int argi1;
+ public int argi2;
+ public int argi3;
+
+ public SomeArgs getNextPoolable() {
+ return mNext;
+ }
+
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ public void setNextPoolable(SomeArgs args) {
+ mNext = args;
+ }
+
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
+
+ private void clear() {
+ arg1 = null;
+ arg2 = null;
+ argi1 = 0;
+ argi2 = 0;
+ argi3 = 0;
+ }
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityIdClientThread(int accessibilityId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+ message.arg1 = accessibilityId;
+ message.arg2 = interactionId;
+ message.obj = callback;
+ sendMessage(message);
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
+ final int accessibilityId = message.arg1;
+ final int interactionId = message.arg2;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) message.obj;
+
+ AccessibilityNodeInfo info = null;
+ try {
+ FindByAccessibilitytIdPredicate predicate = mFindByAccessibilityIdPredicate;
+ predicate.init(accessibilityId);
+ View root = ViewAncestor.this.mView;
+ View target = root.findViewByPredicate(predicate);
+ if (target != null) {
+ info = target.createAccessibilityNodeInfo();
+ }
+ } finally {
+ try {
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfoByViewIdClientThread(int viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.arg1 = viewId;
+ message.arg2 = interactionId;
+ message.obj = callback;
+ sendMessage(message);
+ }
+
+ public void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ final int viewId = message.arg1;
+ final int interactionId = message.arg2;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) message.obj;
+
+ AccessibilityNodeInfo info = null;
+ try {
+ View root = ViewAncestor.this.mView;
+ View target = root.findViewById(viewId);
+ if (target != null) {
+ info = target.createAccessibilityNodeInfo();
+ }
+ } finally {
+ try {
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfosByViewTextClientThread(String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT;
+ SomeArgs args = mPool.acquire();
+ args.arg1 = text;
+ args.argi1 = interactionId;
+ args.arg2 = callback;
+ message.obj = args;
+ sendMessage(message);
+ }
+
+ public void findAccessibilityNodeInfosByViewTextUiThread(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ final String text = (String) args.arg1;
+ final int interactionId = args.argi1;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg2;
+ mPool.release(args);
+
+ List<AccessibilityNodeInfo> infos = null;
+ try {
+ View root = ViewAncestor.this.mView;
+
+ ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
+ foundViews.clear();
+
+ root.findViewsWithText(foundViews, text);
+ if (foundViews.isEmpty()) {
+ return;
+ }
+
+ infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+
+ final int viewCount = foundViews.size();
+ for (int i = 0; i < viewCount; i++) {
+ View foundView = foundViews.get(i);
+ infos.add(foundView.createAccessibilityNodeInfo());
+ }
+ } finally {
+ try {
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void performAccessibilityActionClientThread(int accessibilityId, int action,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback) {
+ Message message = Message.obtain();
+ message.what = DO_PERFORM_ACCESSIBILITY_ACTION;
+ SomeArgs args = mPool.acquire();
+ args.argi1 = accessibilityId;
+ args.argi2 = action;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ sendMessage(message);
+ }
+
+ public void perfromAccessibilityActionUiThread(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ final int accessibilityId = args.argi1;
+ final int action = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+
+ boolean succeeded = false;
+ try {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_FOCUS: {
+ succeeded = performActionFocus(accessibilityId);
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+ succeeded = performActionClearFocus(accessibilityId);
+ } break;
+ case AccessibilityNodeInfo.ACTION_SELECT: {
+ succeeded = performActionSelect(accessibilityId);
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
+ succeeded = performActionClearSelection(accessibilityId);
+ } break;
+ }
+ } finally {
+ try {
+ callback.setPerformAccessibilityActionResult(succeeded, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ private boolean performActionFocus(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ // Get out of touch mode since accessibility wants to move focus around.
+ ensureTouchMode(false);
+ return target.requestFocus();
+ }
+
+ private boolean performActionClearFocus(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ if (!target.isFocused()) {
+ return false;
+ }
+ target.clearFocus();
+ return !target.isFocused();
+ }
+
+ private boolean performActionSelect(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ if (target.isSelected()) {
+ return false;
+ }
+ target.setSelected(true);
+ return target.isSelected();
+ }
+
+ private boolean performActionClearSelection(int accessibilityId) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target == null) {
+ return false;
+ }
+ if (!target.isSelected()) {
+ return false;
+ }
+ target.setSelected(false);
+ return !target.isSelected();
+ }
+
+ private View findViewByAccessibilityId(int accessibilityId) {
+ View root = ViewAncestor.this.mView;
+ if (root == null) {
+ return null;
+ }
+ mFindByAccessibilityIdPredicate.init(accessibilityId);
+ return root.findViewByPredicate(mFindByAccessibilityIdPredicate);
+ }
+
+ private final class FindByAccessibilitytIdPredicate implements Predicate<View> {
+ public int mSerchedId;
+
+ public void init(int searchedId) {
+ mSerchedId = searchedId;
+ }
+
+ public boolean apply(View view) {
+ return (view.getAccessibilityViewId() == mSerchedId);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index df8f7d6..5919150 100755..100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -19,7 +19,6 @@ package android.view;
import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Configuration;
-import android.os.Bundle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.SparseArray;
@@ -96,7 +95,7 @@ public class ViewConfiguration {
* is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
*/
- private static final int TAP_TIMEOUT = 115;
+ private static final int TAP_TIMEOUT = 180;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
@@ -143,12 +142,6 @@ public class ViewConfiguration {
private static final int TOUCH_SLOP = 16;
/**
- * Distance a touch can wander before we think the user is the first touch
- * in a sequence of double tap
- */
- private static final int LARGE_TOUCH_SLOP = 18;
-
- /**
* Distance a touch can wander before we think the user is attempting a paged scroll
* (in dips)
*/
@@ -176,6 +169,13 @@ public class ViewConfiguration {
private static final int MAXIMUM_FLING_VELOCITY = 8000;
/**
+ * Distance between a touch up event denoting the end of a touch exploration
+ * gesture and the touch up event of a subsequent tap for the latter tap to be
+ * considered as a tap i.e. to perform a click.
+ */
+ private static final int TOUCH_EXPLORATION_TAP_SLOP = 80;
+
+ /**
* The maximum size of View's drawing cache, expressed in bytes. This size
* should be at least equal to the size of the screen in ARGB888 format.
*/
@@ -203,9 +203,9 @@ public class ViewConfiguration {
private final int mMaximumFlingVelocity;
private final int mScrollbarSize;
private final int mTouchSlop;
- private final int mLargeTouchSlop;
private final int mPagingTouchSlop;
private final int mDoubleTapSlop;
+ private final int mScaledTouchExplorationTapSlop;
private final int mWindowTouchSlop;
private final int mMaximumDrawingCacheSize;
private final int mOverscrollDistance;
@@ -225,9 +225,9 @@ public class ViewConfiguration {
mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
mScrollbarSize = SCROLL_BAR_SIZE;
mTouchSlop = TOUCH_SLOP;
- mLargeTouchSlop = LARGE_TOUCH_SLOP;
mPagingTouchSlop = PAGING_TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
+ mScaledTouchExplorationTapSlop = TOUCH_EXPLORATION_TAP_SLOP;
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -262,9 +262,9 @@ public class ViewConfiguration {
mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
mTouchSlop = (int) (sizeAndDensity * TOUCH_SLOP + 0.5f);
- mLargeTouchSlop = (int) (sizeAndDensity * LARGE_TOUCH_SLOP + 0.5f);
mPagingTouchSlop = (int) (sizeAndDensity * PAGING_TOUCH_SLOP + 0.5f);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
+ mScaledTouchExplorationTapSlop = (int) (density * TOUCH_EXPLORATION_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
// Size of the screen in bytes, in ARGB_8888 format
@@ -459,14 +459,6 @@ public class ViewConfiguration {
}
/**
- * @return Distance a touch can wander before we think the user is the first touch
- * in a sequence of double tap
- */
- public int getScaledLargeTouchSlop() {
- return mLargeTouchSlop;
- }
-
- /**
* @return Distance a touch can wander before we think the user is scrolling a full page
* in dips
*/
@@ -495,6 +487,17 @@ public class ViewConfiguration {
}
/**
+ * @return Distance between a touch up event denoting the end of a touch exploration
+ * gesture and the touch up event of a subsequent tap for the latter tap to be
+ * considered as a tap i.e. to perform a click.
+ *
+ * @hide
+ */
+ public int getScaledTouchExplorationTapSlop() {
+ return mScaledTouchExplorationTapSlop;
+ }
+
+ /**
* @return Distance a touch must be outside the bounds of a window for it
* to be counted as outside the window for purposes of dismissing that
* window.
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index c19a107..f014070 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -16,7 +16,6 @@
package android.view;
-import android.util.Config;
import android.util.Log;
import android.util.DisplayMetrics;
import android.content.res.Resources;
@@ -93,27 +92,6 @@ public class ViewDebug {
public static final boolean TRACE_RECYCLER = false;
/**
- * Enables or disables motion events tracing. Any invoker of
- * {@link #trace(View, MotionEvent, MotionEventTraceType)} should first check
- * that this value is set to true as not to affect performance.
- *
- * @hide
- */
- public static final boolean TRACE_MOTION_EVENTS = false;
-
- /**
- * The system property of dynamic switch for capturing view information
- * when it is set, we dump interested fields and methods for the view on focus
- */
- static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
-
- /**
- * The system property of dynamic switch for capturing event information
- * when it is set, we log key events, touch/motion and trackball events
- */
- static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
-
- /**
* Profiles drawing times in the events log.
*
* @hide
@@ -141,8 +119,24 @@ public class ViewDebug {
public static final boolean DEBUG_DRAG = false;
/**
+ * Enables logging of factors that affect the latency and responsiveness of an application.
+ *
+ * Logs the relative difference between the time an event was created and the time it
+ * was delivered.
+ *
+ * Logs the time spent waiting for Surface.lockCanvas() or eglSwapBuffers().
+ * This is time that the event loop spends blocked and unresponsive. Ideally, drawing
+ * and animations should be perfectly synchronized with VSYNC so that swap buffers
+ * is instantaneous.
+ *
+ * Logs the time spent in ViewRoot.performTraversals() or ViewRoot.draw().
+ * @hide
+ */
+ public static final boolean DEBUG_LATENCY = false;
+
+ /**
* <p>Enables or disables views consistency check. Even when this property is enabled,
- * view consistency checks happen only if {@link android.util.Config#DEBUG} is set
+ * view consistency checks happen only if {@link false} is set
* to true. The value of this property can be configured externally in one of the
* following files:</p>
* <ul>
@@ -155,12 +149,6 @@ public class ViewDebug {
@Debug.DebugProperty
public static boolean consistencyCheckEnabled = false;
- static {
- if (Config.DEBUG) {
- Debug.setFieldsOn(ViewDebug.class, true);
- }
- }
-
/**
* This annotation can be used to mark fields and methods to be dumped by
* the view server. Only non-void methods with no arguments can be annotated
@@ -360,6 +348,7 @@ public class ViewDebug {
private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
private static final String REMOTE_PROFILE = "PROFILE";
private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
+ private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
private static HashMap<Class<?>, Field[]> sFieldsForClasses;
private static HashMap<Class<?>, Method[]> sMethodsForClasses;
@@ -380,7 +369,7 @@ public class ViewDebug {
}
private static BufferedWriter sHierarchyTraces;
- private static ViewRoot sHierarhcyRoot;
+ private static ViewAncestor sHierarhcyRoot;
private static String sHierarchyTracePrefix;
/**
@@ -408,21 +397,6 @@ public class ViewDebug {
private static String sRecyclerTracePrefix;
/**
- * Defines the type of motion events trace to output to the motion events traces file.
- *
- * @hide
- */
- public enum MotionEventTraceType {
- DISPATCH,
- ON_INTERCEPT,
- ON_TOUCH
- }
-
- private static BufferedWriter sMotionEventTraces;
- private static ViewRoot sMotionEventRoot;
- private static String sMotionEventTracePrefix;
-
- /**
* Returns the number of instanciated Views.
*
* @return The number of Views instanciated in the current process.
@@ -434,14 +408,14 @@ public class ViewDebug {
}
/**
- * Returns the number of instanciated ViewRoots.
+ * Returns the number of instanciated ViewAncestors.
*
- * @return The number of ViewRoots instanciated in the current process.
+ * @return The number of ViewAncestors instanciated in the current process.
*
* @hide
*/
- public static long getViewRootInstanceCount() {
- return Debug.countInstancesOfClass(ViewRoot.class);
+ public static long getViewAncestorInstanceCount() {
+ return Debug.countInstancesOfClass(ViewAncestor.class);
}
/**
@@ -558,6 +532,7 @@ public class ViewDebug {
recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
try {
if (recyclerDump.exists()) {
+ //noinspection ResultOfMethodCallIgnored
recyclerDump.delete();
}
final FileOutputStream file = new FileOutputStream(recyclerDump);
@@ -655,7 +630,7 @@ public class ViewDebug {
return;
}
- sHierarhcyRoot = (ViewRoot) view.getRootView().getParent();
+ sHierarhcyRoot = (ViewAncestor) view.getRootView().getParent();
}
/**
@@ -716,146 +691,6 @@ public class ViewDebug {
sHierarhcyRoot = null;
}
- /**
- * Outputs a trace to the currently opened traces file. The trace contains the class name
- * and instance's hashcode of the specified view as well as the supplied trace type.
- *
- * @param view the view to trace
- * @param event the event of the trace
- * @param type the type of the trace
- *
- * @hide
- */
- public static void trace(View view, MotionEvent event, MotionEventTraceType type) {
- if (sMotionEventTraces == null) {
- return;
- }
-
- try {
- sMotionEventTraces.write(type.name());
- sMotionEventTraces.write(' ');
- sMotionEventTraces.write(event.getAction());
- sMotionEventTraces.write(' ');
- sMotionEventTraces.write(view.getClass().getName());
- sMotionEventTraces.write('@');
- sMotionEventTraces.write(Integer.toHexString(view.hashCode()));
- sHierarchyTraces.newLine();
- } catch (IOException e) {
- Log.w("View", "Error while dumping trace of event " + event + " for view " + view);
- }
- }
-
- /**
- * Starts tracing the motion events for the hierarchy of the specificy view.
- * The trace is identified by a prefix, used to build the traces files names:
- * <code>/EXTERNAL/motion-events/PREFIX.traces</code> and
- * <code>/EXTERNAL/motion-events/PREFIX.tree</code>.
- *
- * Only one view hierarchy can be traced at the same time. After calling this method, any
- * other invocation will result in a <code>IllegalStateException</code> unless
- * {@link #stopMotionEventTracing()} is invoked before.
- *
- * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.traces</code>
- * containing all the traces (or method calls) relative to the specified view's hierarchy.
- *
- * This method will return immediately if TRACE_HIERARCHY is false.
- *
- * @param prefix the traces files name prefix
- * @param view the view whose hierarchy must be traced
- *
- * @see #stopMotionEventTracing()
- * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
- *
- * @hide
- */
- public static void startMotionEventTracing(String prefix, View view) {
- //noinspection PointlessBooleanExpression,ConstantConditions
- if (!TRACE_MOTION_EVENTS) {
- return;
- }
-
- if (sMotionEventRoot != null) {
- throw new IllegalStateException("You must call stopMotionEventTracing() before running" +
- " a new trace!");
- }
-
- File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
- //noinspection ResultOfMethodCallIgnored
- hierarchyDump.mkdirs();
-
- hierarchyDump = new File(hierarchyDump, prefix + ".traces");
- sMotionEventTracePrefix = prefix;
-
- try {
- sMotionEventTraces = new BufferedWriter(new FileWriter(hierarchyDump), 32 * 1024);
- } catch (IOException e) {
- Log.e("View", "Could not dump view hierarchy");
- return;
- }
-
- sMotionEventRoot = (ViewRoot) view.getRootView().getParent();
- }
-
- /**
- * Stops the current motion events tracing. This method closes the file
- * <code>/EXTERNAL/motion-events/PREFIX.traces</code>.
- *
- * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.tree</code>
- * containing the view hierarchy of the view supplied to
- * {@link #startMotionEventTracing(String, View)}.
- *
- * This method will return immediately if TRACE_HIERARCHY is false.
- *
- * @see #startMotionEventTracing(String, View)
- * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
- *
- * @hide
- */
- public static void stopMotionEventTracing() {
- //noinspection PointlessBooleanExpression,ConstantConditions
- if (!TRACE_MOTION_EVENTS) {
- return;
- }
-
- if (sMotionEventRoot == null || sMotionEventTraces == null) {
- throw new IllegalStateException("You must call startMotionEventTracing() before" +
- " stopMotionEventTracing()!");
- }
-
- try {
- sMotionEventTraces.close();
- } catch (IOException e) {
- Log.e("View", "Could not write view traces");
- }
- sMotionEventTraces = null;
-
- File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
- //noinspection ResultOfMethodCallIgnored
- hierarchyDump.mkdirs();
- hierarchyDump = new File(hierarchyDump, sMotionEventTracePrefix + ".tree");
-
- BufferedWriter out;
- try {
- out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
- } catch (IOException e) {
- Log.e("View", "Could not dump view hierarchy");
- return;
- }
-
- View view = sMotionEventRoot.getView();
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
- dumpViewHierarchy(group, out, 0);
- try {
- out.close();
- } catch (IOException e) {
- Log.e("View", "Could not dump view hierarchy");
- }
- }
-
- sHierarhcyRoot = null;
- }
-
static void dispatchCommand(View view, String command, String parameters,
OutputStream clientStream) throws IOException {
@@ -870,6 +705,8 @@ public class ViewDebug {
final String[] params = parameters.split(" ");
if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
capture(view, clientStream, params[0]);
+ } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
+ outputDisplayList(view, params[0]);
} else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
invalidate(view, params[0]);
} else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
@@ -1051,8 +888,10 @@ public class ViewDebug {
try {
T[] data = operation.pre();
long start = Debug.threadCpuTimeNanos();
+ //noinspection unchecked
operation.run(data);
duration[0] = Debug.threadCpuTimeNanos() - start;
+ //noinspection unchecked
operation.post(data);
} finally {
latch.countDown();
@@ -1141,6 +980,11 @@ public class ViewDebug {
}
}
+ private static void outputDisplayList(View root, String parameter) throws IOException {
+ final View view = findView(root, parameter);
+ view.getViewAncestor().outputDisplayList(view);
+ }
+
private static void capture(View root, final OutputStream clientStream, String parameter)
throws IOException {
@@ -1178,12 +1022,7 @@ public class ViewDebug {
cache[0] = captureView.createSnapshot(
Bitmap.Config.ARGB_8888, 0, skpiChildren);
} catch (OutOfMemoryError e) {
- try {
- cache[0] = captureView.createSnapshot(
- Bitmap.Config.ARGB_4444, 0, skpiChildren);
- } catch (OutOfMemoryError e2) {
- Log.w("View", "Out of memory for bitmap");
- }
+ Log.w("View", "Out of memory for bitmap");
} finally {
latch.countDown();
}
@@ -1293,7 +1132,6 @@ public class ViewDebug {
}
final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
- final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
Field[] fields = map.get(klass);
if (fields != null) {
@@ -1309,7 +1147,7 @@ public class ViewDebug {
if (field.isAnnotationPresent(ExportedProperty.class)) {
field.setAccessible(true);
foundFields.add(field);
- annotations.put(field, field.getAnnotation(ExportedProperty.class));
+ sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
}
}
@@ -1328,7 +1166,6 @@ public class ViewDebug {
}
final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
- final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
Method[] methods = map.get(klass);
if (methods != null) {
@@ -1346,7 +1183,7 @@ public class ViewDebug {
method.getReturnType() != Void.class) {
method.setAccessible(true);
foundMethods.add(method);
- annotations.put(method, method.getAnnotation(ExportedProperty.class));
+ sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8dc86ac..57ee8a0 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -35,6 +35,7 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -129,11 +130,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// First touch target in the linked list of touch targets.
private TouchTarget mFirstTouchTarget;
- // Temporary arrays for splitting pointers.
- private int[] mTmpPointerIndexMap;
- private int[] mTmpPointerIds;
- private MotionEvent.PointerCoords[] mTmpPointerCoords;
-
// For debugging only. You can see these in hierarchyviewer.
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
@ViewDebug.ExportedProperty(category = "events")
@@ -147,6 +143,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@ViewDebug.ExportedProperty(category = "events")
private float mLastTouchDownY;
+ // Child which last received ACTION_HOVER_ENTER and ACTION_HOVER_MOVE.
+ private View mHoveredChild;
+
/**
* Internal flags.
*
@@ -533,7 +532,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// note: knowing that mFocused is non-null is not a good enough reason
// to break the traversal since in that case we'd actually have to find
// the focused view and make sure it wasn't FOCUS_AFTER_DESCENDANTS and
- // an ancestor of v; this will get checked for at ViewRoot
+ // an ancestor of v; this will get checked for at ViewAncestor
&& !(isFocused() && getDescendantFocusability() != FOCUS_AFTER_DESCENDANTS)) {
mParent.focusableViewAvailable(v);
}
@@ -583,6 +582,36 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* {@inheritDoc}
*/
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ ViewParent parent = getParent();
+ if (parent == null) {
+ return false;
+ }
+ final boolean propagate = onRequestSendAccessibilityEvent(child, event);
+ //noinspection SimplifiableIfStatement
+ if (!propagate) {
+ return false;
+ }
+ return parent.requestSendAccessibilityEvent(this, event);
+ }
+
+ /**
+ * Called when a child has requested sending an {@link AccessibilityEvent} and
+ * gives an opportunity to its parent to augment the event.
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event should be sent.
+ *
+ * @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
+ */
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
return mFocused != null &&
@@ -744,6 +773,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ @Override
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ child.findViewsWithText(outViews, text);
+ }
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -904,7 +945,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float tx = event.mX;
final float ty = event.mY;
- ViewRoot root = getViewRoot();
+ ViewAncestor root = getViewAncestor();
// Dispatch down the view hierarchy
switch (event.mAction) {
@@ -926,6 +967,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
+ child.mPrivateFlags2 &= ~View.DRAG_MASK;
if (child.getVisibility() == VISIBLE) {
final boolean handled = notifyChildOfDrag(children[i]);
if (handled) {
@@ -946,6 +988,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (View child : mDragNotifiedChildren) {
// If a child was notified about an ongoing drag, it's told that it's over
child.dispatchDragEvent(event);
+ child.mPrivateFlags2 &= ~View.DRAG_MASK;
+ child.refreshDrawableState();
}
mDragNotifiedChildren.clear();
@@ -976,8 +1020,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int action = event.mAction;
// If we've dragged off of a child view, send it the EXITED message
if (mCurrentDragView != null) {
+ final View view = mCurrentDragView;
event.mAction = DragEvent.ACTION_DRAG_EXITED;
- mCurrentDragView.dispatchDragEvent(event);
+ view.dispatchDragEvent(event);
+ view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+ view.refreshDrawableState();
}
mCurrentDragView = target;
@@ -985,6 +1032,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (target != null) {
event.mAction = DragEvent.ACTION_DRAG_ENTERED;
target.dispatchDragEvent(event);
+ target.mPrivateFlags2 |= View.DRAG_HOVERED;
+ target.refreshDrawableState();
}
event.mAction = action; // restore the event's original state
}
@@ -1015,7 +1064,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
case DragEvent.ACTION_DRAG_EXITED: {
if (mCurrentDragView != null) {
- mCurrentDragView.dispatchDragEvent(event);
+ final View view = mCurrentDragView;
+ view.dispatchDragEvent(event);
+ view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+ view.refreshDrawableState();
+
mCurrentDragView = null;
}
} break;
@@ -1053,7 +1106,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View[] children = mChildren;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
- if (!child.mCanAcceptDrop) {
+ if (!child.canAcceptDrag()) {
continue;
}
@@ -1069,11 +1122,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
Log.d(View.VIEW_LOG_TAG, "Sending drag-started to view: " + child);
}
+ boolean canAccept = false;
if (! mDragNotifiedChildren.contains(child)) {
mDragNotifiedChildren.add(child);
- child.mCanAcceptDrop = child.dispatchDragEvent(mCurrentDrag);
+ canAccept = child.dispatchDragEvent(mCurrentDrag);
+ if (canAccept && !child.canAcceptDrag()) {
+ child.mPrivateFlags2 |= View.DRAG_CAN_ACCEPT;
+ child.refreshDrawableState();
+ }
}
- return child.mCanAcceptDrop;
+ return canAccept;
}
@Override
@@ -1106,10 +1164,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 1);
+ }
+
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchKeyEvent(event);
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchKeyEvent(event);
+ if (mFocused.dispatchKeyEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@@ -1132,21 +1202,70 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 1);
+ }
+
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchTrackballEvent(event);
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchTrackballEvent(event);
+ if (mFocused.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
- /**
- * {@inheritDoc}
- */
+ /** @hide */
@Override
- public boolean dispatchGenericMotionEvent(MotionEvent event) {
- if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- // Send the event to the child under the pointer.
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ // Send the hover enter or hover move event to the view group first.
+ // If it handles the event then a hovered child should receive hover exit.
+ boolean handled = false;
+ final boolean interceptHover;
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ interceptHover = true;
+ } else {
+ handled = super.dispatchHoverEvent(event);
+ interceptHover = handled;
+ }
+
+ // Send successive hover events to the hovered child as long as the pointer
+ // remains within the child's bounds.
+ MotionEvent eventNoHistory = event;
+ if (mHoveredChild != null) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (interceptHover
+ || !isTransformedTouchPointInView(x, y, mHoveredChild, null)) {
+ // Pointer exited the child.
+ // Send it a hover exit with only the most recent coordinates. We could
+ // try to find the exact point in history when the pointer left the view
+ // but it is not worth the effort.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild);
+ eventNoHistory.setAction(action);
+ mHoveredChild = null;
+ } else {
+ // Pointer is still within the child.
+ //noinspection ConstantConditions
+ handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild);
+ }
+ }
+
+ // Find a new hovered child if needed.
+ if (!interceptHover && mHoveredChild == null
+ && (action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE)) {
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final View[] children = mChildren;
@@ -1155,45 +1274,100 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
- && child.getAnimation() == null) {
- // Skip invisible child unless it is animating.
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
- if (!isTransformedTouchPointInView(x, y, child, null)) {
- // Scroll point is out of child's bounds.
- continue;
+ // Found the hovered child.
+ mHoveredChild = child;
+ if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ // Pointer was moving within the view group and entered the child.
+ // Send it a hover enter and hover move with only the most recent
+ // coordinates. We could try to find the exact point in history when
+ // the pointer entered the view but it is not worth the effort.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
+ eventNoHistory.setAction(action);
+
+ handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
+ } else { /* must be ACTION_HOVER_ENTER */
+ // Pointer entered the child.
+ handled |= dispatchTransformedGenericPointerEvent(event, child);
}
+ break;
+ }
+ }
+ }
- final float offsetX = mScrollX - child.mLeft;
- final float offsetY = mScrollY - child.mTop;
- final boolean handled;
- if (!child.hasIdentityMatrix()) {
- MotionEvent transformedEvent = MotionEvent.obtain(event);
- transformedEvent.offsetLocation(offsetX, offsetY);
- transformedEvent.transform(child.getInverseMatrix());
- handled = child.dispatchGenericMotionEvent(transformedEvent);
- transformedEvent.recycle();
- } else {
- event.offsetLocation(offsetX, offsetY);
- handled = child.dispatchGenericMotionEvent(event);
- event.offsetLocation(-offsetX, -offsetY);
- }
+ // Recycle the copy of the event that we made.
+ if (eventNoHistory != event) {
+ eventNoHistory.recycle();
+ }
- if (handled) {
- return true;
- }
+ // Send hover exit to the view group. If there was a child, we will already have
+ // sent the hover exit to it.
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ handled |= super.dispatchHoverEvent(event);
+ }
+
+ // Done.
+ return handled;
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ // Handle the event only if leaf. This guarantees that
+ // the leafs (or any custom class that returns true from
+ // this method) will get a change to process the hover.
+ //noinspection SimplifiableIfStatement
+ if (getChildCount() == 0) {
+ return super.onHoverEvent(event);
+ }
+ return false;
+ }
+
+ private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
+ if (event.getHistorySize() == 0) {
+ return event;
+ }
+ return MotionEvent.obtainNoHistory(event);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+ // Send the event to the child under the pointer.
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ final View[] children = mChildren;
+ final float x = event.getX();
+ final float y = event.getY();
+
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
}
- }
- // No child handled the event. Send it to this view group.
- return super.dispatchGenericMotionEvent(event);
+ if (dispatchTransformedGenericPointerEvent(event, child)) {
+ return true;
+ }
+ }
}
+ // No child handled the event. Send it to this view group.
+ return super.dispatchGenericPointerEvent(event);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
// Send the event to the focused child or to this view group if it has focus.
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchGenericMotionEvent(event);
+ return super.dispatchGenericFocusedEvent(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
return mFocused.dispatchGenericMotionEvent(event);
}
@@ -1201,166 +1375,193 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Dispatches a generic pointer event to a child, taking into account
+ * transformations that apply to the child.
+ *
+ * @param event The event to send.
+ * @param child The view to send the event to.
+ * @return {@code true} if the child handled the event.
+ */
+ private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+
+ boolean handled;
+ if (!child.hasIdentityMatrix()) {
+ MotionEvent transformedEvent = MotionEvent.obtain(event);
+ transformedEvent.offsetLocation(offsetX, offsetY);
+ transformedEvent.transform(child.getInverseMatrix());
+ handled = child.dispatchGenericMotionEvent(transformedEvent);
+ transformedEvent.recycle();
+ } else {
+ event.offsetLocation(offsetX, offsetY);
+ handled = child.dispatchGenericMotionEvent(event);
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return handled;
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (!onFilterTouchEventForSecurity(ev)) {
- return false;
- }
-
- final int action = ev.getAction();
- final int actionMasked = action & MotionEvent.ACTION_MASK;
-
- // Handle an initial down.
- if (actionMasked == MotionEvent.ACTION_DOWN
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- // Throw away all previous state when starting a new touch gesture.
- // The framework may have dropped the up or cancel event for the previous gesture
- // due to an app switch, ANR, or some other state change.
- cancelAndClearTouchTargets(ev);
- resetTouchState();
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
- // Check for interception.
- final boolean intercepted;
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (!disallowIntercept) {
- intercepted = onInterceptTouchEvent(ev);
- ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
- } else {
- intercepted = false;
+ boolean handled = false;
+ if (onFilterTouchEventForSecurity(ev)) {
+ final int action = ev.getAction();
+ final int actionMasked = action & MotionEvent.ACTION_MASK;
+
+ // Handle an initial down.
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Throw away all previous state when starting a new touch gesture.
+ // The framework may have dropped the up or cancel event for the previous gesture
+ // due to an app switch, ANR, or some other state change.
+ cancelAndClearTouchTargets(ev);
+ resetTouchState();
}
- } else {
- // There are no touch targets and this action is not an initial down
- // so this view group continues to intercept touches.
- intercepted = true;
- }
-
- // Check for cancelation.
- final boolean canceled = resetCancelNextUpFlag(this)
- || actionMasked == MotionEvent.ACTION_CANCEL;
- // Update list of touch targets for pointer down, if needed.
- final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
- TouchTarget newTouchTarget = null;
- boolean alreadyDispatchedToNewTouchTarget = false;
- if (!canceled && !intercepted) {
+ // Check for interception.
+ final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
- || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- final int actionIndex = ev.getActionIndex(); // always 0 for down
- final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
- : TouchTarget.ALL_POINTER_IDS;
-
- // Clean up earlier touch targets for this pointer id in case they
- // have become out of sync.
- removePointersFromTouchTargets(idBitsToAssign);
-
- final int childrenCount = mChildrenCount;
- if (childrenCount != 0) {
- // 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);
-
- for (int i = childrenCount - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
- && child.getAnimation() == null) {
- // Skip invisible child unless it is animating.
- continue;
- }
+ || mFirstTouchTarget != null) {
+ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+ if (!disallowIntercept) {
+ intercepted = onInterceptTouchEvent(ev);
+ ev.setAction(action); // restore action in case it was changed
+ } else {
+ intercepted = false;
+ }
+ } else {
+ // There are no touch targets and this action is not an initial down
+ // so this view group continues to intercept touches.
+ intercepted = true;
+ }
- if (!isTransformedTouchPointInView(x, y, child, null)) {
- // New pointer is out of child's bounds.
- continue;
- }
+ // Check for cancelation.
+ final boolean canceled = resetCancelNextUpFlag(this)
+ || actionMasked == MotionEvent.ACTION_CANCEL;
+
+ // Update list of touch targets for pointer down, if needed.
+ final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+ TouchTarget newTouchTarget = null;
+ boolean alreadyDispatchedToNewTouchTarget = false;
+ if (!canceled && !intercepted) {
+ if (actionMasked == MotionEvent.ACTION_DOWN
+ || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ final int actionIndex = ev.getActionIndex(); // always 0 for down
+ final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+ : TouchTarget.ALL_POINTER_IDS;
+
+ // Clean up earlier touch targets for this pointer id in case they
+ // have become out of sync.
+ removePointersFromTouchTargets(idBitsToAssign);
+
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ // 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);
+
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
- newTouchTarget = getTouchTarget(child);
- if (newTouchTarget != null) {
- // Child is already receiving touch within its bounds.
- // Give it the new pointer in addition to the ones it is handling.
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- break;
- }
+ newTouchTarget = getTouchTarget(child);
+ if (newTouchTarget != null) {
+ // Child is already receiving touch within its bounds.
+ // Give it the new pointer in addition to the ones it is handling.
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ break;
+ }
- resetCancelNextUpFlag(child);
- if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
- // Child wants to receive touch within its bounds.
- mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = i;
- mLastTouchDownX = ev.getX();
- mLastTouchDownY = ev.getY();
- newTouchTarget = addTouchTarget(child, idBitsToAssign);
- alreadyDispatchedToNewTouchTarget = true;
- break;
+ resetCancelNextUpFlag(child);
+ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+ // Child wants to receive touch within its bounds.
+ mLastTouchDownTime = ev.getDownTime();
+ mLastTouchDownIndex = i;
+ mLastTouchDownX = ev.getX();
+ mLastTouchDownY = ev.getY();
+ newTouchTarget = addTouchTarget(child, idBitsToAssign);
+ alreadyDispatchedToNewTouchTarget = true;
+ break;
+ }
}
}
- }
- if (newTouchTarget == null && mFirstTouchTarget != null) {
- // Did not find a child to receive the event.
- // Assign the pointer to the least recently added target.
- newTouchTarget = mFirstTouchTarget;
- while (newTouchTarget.next != null) {
- newTouchTarget = newTouchTarget.next;
+ if (newTouchTarget == null && mFirstTouchTarget != null) {
+ // Did not find a child to receive the event.
+ // Assign the pointer to the least recently added target.
+ newTouchTarget = mFirstTouchTarget;
+ while (newTouchTarget.next != null) {
+ newTouchTarget = newTouchTarget.next;
+ }
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
}
- newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
- }
- // Dispatch to touch targets.
- boolean handled = false;
- if (mFirstTouchTarget == null) {
- // No touch targets so treat this as an ordinary view.
- handled = dispatchTransformedTouchEvent(ev, canceled, null,
- TouchTarget.ALL_POINTER_IDS);
- } else {
- // Dispatch to touch targets, excluding the new touch target if we already
- // dispatched to it. Cancel touch targets if necessary.
- TouchTarget predecessor = null;
- TouchTarget target = mFirstTouchTarget;
- while (target != null) {
- final TouchTarget next = target.next;
- if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
- handled = true;
- } else {
- final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
- if (dispatchTransformedTouchEvent(ev, cancelChild,
- target.child, target.pointerIdBits)) {
+ // Dispatch to touch targets.
+ if (mFirstTouchTarget == null) {
+ // No touch targets so treat this as an ordinary view.
+ handled = dispatchTransformedTouchEvent(ev, canceled, null,
+ TouchTarget.ALL_POINTER_IDS);
+ } else {
+ // Dispatch to touch targets, excluding the new touch target if we already
+ // dispatched to it. Cancel touch targets if necessary.
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
- }
- if (cancelChild) {
- if (predecessor == null) {
- mFirstTouchTarget = next;
- } else {
- predecessor.next = next;
+ } else {
+ final boolean cancelChild = resetCancelNextUpFlag(target.child)
+ || intercepted;
+ if (dispatchTransformedTouchEvent(ev, cancelChild,
+ target.child, target.pointerIdBits)) {
+ handled = true;
+ }
+ if (cancelChild) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
+ continue;
}
- target.recycle();
- target = next;
- continue;
}
+ predecessor = target;
+ target = next;
}
- predecessor = target;
- target = next;
}
- }
- // Update list of touch targets for pointer up or cancel, if needed.
- if (canceled
- || actionMasked == MotionEvent.ACTION_UP
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- resetTouchState();
- } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
- final int actionIndex = ev.getActionIndex();
- final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
- removePointersFromTouchTargets(idBitsToRemove);
+ // Update list of touch targets for pointer up or cancel, if needed.
+ if (canceled
+ || actionMasked == MotionEvent.ACTION_UP
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ resetTouchState();
+ } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ final int actionIndex = ev.getActionIndex();
+ final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+ removePointersFromTouchTargets(idBitsToRemove);
+ }
}
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
+ }
return handled;
}
@@ -1476,6 +1677,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Returns true if a child view can receive pointer events.
+ * @hide
+ */
+ private static boolean canViewReceivePointerEvents(View child) {
+ return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ || child.getAnimation() != null;
+ }
+
+ /**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
@@ -1524,141 +1734,38 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
// Calculate the number of pointers to deliver.
- final int oldPointerCount = event.getPointerCount();
- int newPointerCount = 0;
- if (desiredPointerIdBits == TouchTarget.ALL_POINTER_IDS) {
- newPointerCount = oldPointerCount;
- } else {
- for (int i = 0; i < oldPointerCount; i++) {
- final int pointerId = event.getPointerId(i);
- final int pointerIdBit = 1 << pointerId;
- if ((pointerIdBit & desiredPointerIdBits) != 0) {
- newPointerCount += 1;
- }
- }
- }
+ final int oldPointerIdBits = event.getPointerIdBits();
+ final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
- if (newPointerCount == 0) {
+ if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
- final boolean reuse = newPointerCount == oldPointerCount
- && (child == null || child.hasIdentityMatrix());
- if (reuse) {
- if (child == null) {
- handled = super.dispatchTouchEvent(event);
- } else {
- final float offsetX = mScrollX - child.mLeft;
- final float offsetY = mScrollY - child.mTop;
- event.offsetLocation(offsetX, offsetY);
-
- handled = child.dispatchTouchEvent(event);
-
- event.offsetLocation(-offsetX, -offsetY);
- }
- return handled;
- }
-
- // Make a copy of the event.
- // If the number of pointers is different, then we need to filter out irrelevant pointers
- // as we make a copy of the motion event.
- MotionEvent transformedEvent;
- if (newPointerCount == oldPointerCount) {
- transformedEvent = MotionEvent.obtain(event);
- } else {
- growTmpPointerArrays(newPointerCount);
- final int[] newPointerIndexMap = mTmpPointerIndexMap;
- final int[] newPointerIds = mTmpPointerIds;
- final MotionEvent.PointerCoords[] newPointerCoords = mTmpPointerCoords;
-
- int newPointerIndex = 0;
- int oldPointerIndex = 0;
- while (newPointerIndex < newPointerCount) {
- final int pointerId = event.getPointerId(oldPointerIndex);
- final int pointerIdBits = 1 << pointerId;
- if ((pointerIdBits & desiredPointerIdBits) != 0) {
- newPointerIndexMap[newPointerIndex] = oldPointerIndex;
- newPointerIds[newPointerIndex] = pointerId;
- if (newPointerCoords[newPointerIndex] == null) {
- newPointerCoords[newPointerIndex] = new MotionEvent.PointerCoords();
- }
-
- newPointerIndex += 1;
- }
- oldPointerIndex += 1;
- }
-
- final int newAction;
- if (cancel) {
- newAction = MotionEvent.ACTION_CANCEL;
- } else {
- final int oldMaskedAction = oldAction & MotionEvent.ACTION_MASK;
- if (oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
- || oldMaskedAction == MotionEvent.ACTION_POINTER_UP) {
- final int changedPointerId = event.getPointerId(
- (oldAction & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
- final int changedPointerIdBits = 1 << changedPointerId;
- if ((changedPointerIdBits & desiredPointerIdBits) != 0) {
- if (newPointerCount == 1) {
- // The first/last pointer went down/up.
- newAction = oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
- ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP;
- } else {
- // A secondary pointer went down/up.
- int newChangedPointerIndex = 0;
- while (newPointerIds[newChangedPointerIndex] != changedPointerId) {
- newChangedPointerIndex += 1;
- }
- newAction = oldMaskedAction | (newChangedPointerIndex
- << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
- }
- } else {
- // An unrelated pointer changed.
- newAction = MotionEvent.ACTION_MOVE;
- }
+ // Otherwise we need to make a copy.
+ final MotionEvent transformedEvent;
+ if (newPointerIdBits == oldPointerIdBits) {
+ if (child == null || child.hasIdentityMatrix()) {
+ if (child == null) {
+ handled = super.dispatchTouchEvent(event);
} else {
- // Simple up/down/cancel/move motion action.
- newAction = oldMaskedAction;
- }
- }
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ event.offsetLocation(offsetX, offsetY);
- transformedEvent = null;
- final int historySize = event.getHistorySize();
- for (int historyIndex = 0; historyIndex <= historySize; historyIndex++) {
- for (newPointerIndex = 0; newPointerIndex < newPointerCount; newPointerIndex++) {
- final MotionEvent.PointerCoords c = newPointerCoords[newPointerIndex];
- oldPointerIndex = newPointerIndexMap[newPointerIndex];
- if (historyIndex != historySize) {
- event.getHistoricalPointerCoords(oldPointerIndex, historyIndex, c);
- } else {
- event.getPointerCoords(oldPointerIndex, c);
- }
- }
+ handled = child.dispatchTouchEvent(event);
- final long eventTime;
- if (historyIndex != historySize) {
- eventTime = event.getHistoricalEventTime(historyIndex);
- } else {
- eventTime = event.getEventTime();
- }
-
- if (transformedEvent == null) {
- transformedEvent = MotionEvent.obtain(
- event.getDownTime(), eventTime, newAction,
- newPointerCount, newPointerIds, newPointerCoords,
- event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
- event.getDeviceId(), event.getEdgeFlags(), event.getSource(),
- event.getFlags());
- } else {
- transformedEvent.addBatch(eventTime, newPointerCoords, 0);
+ event.offsetLocation(-offsetX, -offsetY);
}
+ return handled;
}
+ transformedEvent = MotionEvent.obtain(event);
+ } else {
+ transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
@@ -1681,36 +1788,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Enlarge the temporary pointer arrays for splitting pointers.
- * May discard contents (but keeps PointerCoords objects to avoid reallocating them).
- */
- private void growTmpPointerArrays(int desiredCapacity) {
- final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords;
- int capacity;
- if (oldTmpPointerCoords != null) {
- capacity = oldTmpPointerCoords.length;
- if (desiredCapacity <= capacity) {
- return;
- }
- } else {
- capacity = 4;
- }
-
- while (capacity < desiredCapacity) {
- capacity *= 2;
- }
-
- mTmpPointerIndexMap = new int[capacity];
- mTmpPointerIds = new int[capacity];
- mTmpPointerCoords = new MotionEvent.PointerCoords[capacity];
-
- if (oldTmpPointerCoords != null) {
- System.arraycopy(oldTmpPointerCoords, 0, mTmpPointerCoords, 0,
- oldTmpPointerCoords.length);
- }
- }
-
- /**
* Enable or disable the splitting of MotionEvents to multiple children during touch event
* dispatch. This behavior is enabled by default for applications that target an
* SDK version of {@link Build.VERSION_CODES#HONEYCOMB} or newer.
@@ -1818,7 +1895,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @see #FOCUS_BEFORE_DESCENDANTS
* @see #FOCUS_AFTER_DESCENDANTS
* @see #FOCUS_BLOCK_DESCENDANTS
- * @see #onRequestFocusInDescendants
+ * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
*/
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
@@ -1931,11 +2008,26 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = false;
+ // We first get a chance to populate the event.
+ onPopulateAccessibilityEvent(event);
+ // Let our children have a shot in populating the event.
for (int i = 0, count = getChildCount(); i < count; i++) {
- populated |= getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ boolean handled = getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ if (handled) {
+ return handled;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ for (int i = 0, count = mChildrenCount; i < count; i++) {
+ View child = mChildren[i];
+ info.addChild(child);
}
- return populated;
}
/**
@@ -1975,7 +2067,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
- if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingRight) != 0) {
+ if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingBottom) != 0) {
mGroupFlags |= FLAG_PADDING_NOT_NULL;
} else {
mGroupFlags &= ~FLAG_PADDING_NOT_NULL;
@@ -1999,10 +2091,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Perform dispatching of a {@link #saveHierarchyState freeze()} to only this view,
- * not to its children. For use when overriding
- * {@link #dispatchSaveInstanceState dispatchFreeze()} to allow subclasses to freeze
- * their own state but not the state of their children.
+ * Perform dispatching of a {@link #saveHierarchyState(android.util.SparseArray)} freeze()}
+ * to only this view, not to its children. For use when overriding
+ * {@link #dispatchSaveInstanceState(android.util.SparseArray)} dispatchFreeze()} to allow
+ * subclasses to freeze their own state but not the state of their children.
*
* @param container the container
*/
@@ -2027,10 +2119,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Perform dispatching of a {@link #restoreHierarchyState thaw()} to only this view,
- * not to its children. For use when overriding
- * {@link #dispatchRestoreInstanceState dispatchThaw()} to allow subclasses to thaw
- * their own state but not the state of their children.
+ * Perform dispatching of a {@link #restoreHierarchyState(android.util.SparseArray)}
+ * to only this view, not to its children. For use when overriding
+ * {@link #dispatchRestoreInstanceState(android.util.SparseArray)} to allow
+ * subclasses to thaw their own state but not the state of their children.
*
* @param container the container
*/
@@ -3244,6 +3336,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
+ if (view == mHoveredChild) {
+ mHoveredChild = null;
+ }
+
boolean clearChildFocus = false;
if (view == mFocused) {
view.clearFocusForRemoval();
@@ -3307,6 +3403,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener;
final boolean notifyListener = onHierarchyChangeListener != null;
final View focused = mFocused;
+ final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3320,6 +3417,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
+ if (view == hoveredChild) {
+ mHoveredChild = null;
+ }
+
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;
@@ -3377,6 +3478,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final OnHierarchyChangeListener listener = mOnHierarchyChangeListener;
final boolean notify = listener != null;
final View focused = mFocused;
+ final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3389,6 +3491,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
+ if (view == hoveredChild) {
+ mHoveredChild = null;
+ }
+
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;
@@ -3610,13 +3716,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= DRAW_ANIMATION;
- } else if (parent instanceof ViewRoot) {
- ((ViewRoot) parent).mIsAnimating = true;
+ } else if (parent instanceof ViewAncestor) {
+ ((ViewAncestor) parent).mIsAnimating = true;
}
}
- if (parent instanceof ViewRoot) {
- ((ViewRoot) parent).invalidate();
+ if (parent instanceof ViewAncestor) {
+ ((ViewAncestor) parent).invalidate();
parent = null;
} else if (view != null) {
if ((view.mPrivateFlags & DRAWN) == DRAWN ||
@@ -3671,8 +3777,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= DRAW_ANIMATION;
- } else if (parent instanceof ViewRoot) {
- ((ViewRoot) parent).mIsAnimating = true;
+ } else if (parent instanceof ViewAncestor) {
+ ((ViewAncestor) parent).mIsAnimating = true;
}
}
@@ -4195,7 +4301,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// If this group is dirty, check that the parent is dirty as well
if ((mPrivateFlags & DIRTY_MASK) != 0) {
final ViewParent parent = getParent();
- if (parent != null && !(parent instanceof ViewRoot)) {
+ if (parent != null && !(parent instanceof ViewAncestor)) {
if ((((View) parent).mPrivateFlags & DIRTY_MASK) == 0) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
@@ -4755,6 +4861,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * This method is called by LayoutTransition when there are 'changing' animations that need
+ * to start after the layout/setup phase. The request is forwarded to the ViewAncestor, who
+ * starts all pending transitions prior to the drawing phase in the current traversal.
+ *
+ * @param transition The LayoutTransition to be started on the next traversal.
+ *
+ * @hide
+ */
+ public void requestTransitionStart(LayoutTransition transition) {
+ ViewAncestor viewAncestor = getViewAncestor();
+ viewAncestor.requestTransitionStart(transition);
+ }
+
+ /**
+ * Return true if the pressed state should be delayed for children or descendants of this
+ * ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
+ * This prevents the pressed state from appearing when the user is actually trying to scroll
+ * the content.
+ *
+ * The default implementation returns true for compatibility reasons. Subclasses that do
+ * not scroll should generally override this method and return false.
+ */
+ public boolean shouldDelayChildPressedState() {
+ return true;
+ }
+
+ /**
* LayoutParams are used by views to tell their parents how they want to be
* laid out. See
* {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index d7d4c3f..655df39 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.Rect;
+import android.view.accessibility.AccessibilityEvent;
/**
* Defines the responsibilities for a class that will be a parent of a View.
@@ -222,4 +223,22 @@ public interface ViewParent {
*/
public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
boolean immediate);
+
+ /**
+ * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
+ * The child has already populated a record for itself in the event and is delegating
+ * to its parent to send the event. The parent can optionally add a record for itself.
+ * <p>
+ * Note: An accessibility event is fired by an individual view which populates the
+ * event with a record for its state and requests from its parent to perform
+ * the sending. The parent can optionally add a record for itself before
+ * dispatching the request to its parent. A parent can also choose not to
+ * respect the request for sending the event. The accessibility event is sent
+ * by the topmost view in the view tree.
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event was sent.
+ */
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event);
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 1d56e9d..9eddf23 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -67,6 +67,19 @@ public class ViewPropertyAnimator {
private boolean mDurationSet = false;
/**
+ * The startDelay of the underlying Animator object. By default, we don't set the startDelay
+ * on the Animator and just use its default startDelay. If the startDelay is ever set on this
+ * Animator, then we use the startDelay that it was set to.
+ */
+ private long mStartDelay = 0;
+
+ /**
+ * A flag indicating whether the startDelay has been set on this object. If not, we don't set
+ * the startDelay on the underlying Animator, but instead just use its default startDelay.
+ */
+ private boolean mStartDelaySet = false;
+
+ /**
* The interpolator of the underlying Animator object. By default, we don't set the interpolator
* on the Animator and just use its default interpolator. If the interpolator is ever set on
* this Animator, then we use the interpolator that it was set to.
@@ -233,6 +246,60 @@ public class ViewPropertyAnimator {
}
/**
+ * Returns the current duration of property animations. If the duration was set on this
+ * object, that value is returned. Otherwise, the default value of the underlying Animator
+ * is returned.
+ *
+ * @see #setDuration(long)
+ * @return The duration of animations, in milliseconds.
+ */
+ public long getDuration() {
+ if (mStartDelaySet) {
+ return mStartDelay;
+ } else {
+ // Just return the default from ValueAnimator, since that's what we'd get if
+ // the value has not been set otherwise
+ return new ValueAnimator().getDuration();
+ }
+ }
+
+ /**
+ * Returns the current startDelay of property animations. If the startDelay was set on this
+ * object, that value is returned. Otherwise, the default value of the underlying Animator
+ * is returned.
+ *
+ * @see #setStartDelay(long)
+ * @return The startDelay of animations, in milliseconds.
+ */
+ public long getStartDelay() {
+ if (mStartDelaySet) {
+ return mStartDelay;
+ } else {
+ // Just return the default from ValueAnimator (0), since that's what we'd get if
+ // the value has not been set otherwise
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the startDelay for the underlying animator that animates the requested properties.
+ * By default, the animator uses the default value for ValueAnimator. Calling this method
+ * will cause the declared value to be used instead.
+ * @param startDelay The delay of ensuing property animations, in milliseconds. The value
+ * cannot be negative.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator setStartDelay(long startDelay) {
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("Animators cannot have negative duration: " +
+ startDelay);
+ }
+ mStartDelaySet = true;
+ mStartDelay = startDelay;
+ return this;
+ }
+
+ /**
* Sets the interpolator for the underlying animator that animates the requested properties.
* By default, the animator uses the default interpolator for ValueAnimator. Calling this method
* will cause the declared object to be used instead.
@@ -259,6 +326,33 @@ public class ViewPropertyAnimator {
}
/**
+ * Starts the currently pending property animations immediately. Calling <code>start()</code>
+ * is optional because all animations start automatically at the next opportunity. However,
+ * if the animations are needed to start immediately and synchronously (not at the time when
+ * the next event is processed by the hierarchy, which is when the animations would begin
+ * otherwise), then this method can be used.
+ */
+ public void start() {
+ startAnimation();
+ }
+
+ /**
+ * Cancels all property animations that are currently running or pending.
+ */
+ public void cancel() {
+ if (mAnimatorMap.size() > 0) {
+ HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
+ (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
+ Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
+ for (Animator runningAnim : animatorSet) {
+ runningAnim.cancel();
+ }
+ }
+ mPendingAnimations.clear();
+ mView.getHandler().removeCallbacks(mAnimationStarter);
+ }
+
+ /**
* This method will cause the View's <code>x</code> property to be animated to the
* specified value. Animations already running on the property will be canceled.
*
@@ -598,7 +692,7 @@ public class ViewPropertyAnimator {
// on a property will cancel a previous animation on that property, so
// there can only ever be one such animation running.
if (bundle.mPropertyMask == NONE) {
- // the animation is not longer changing anything - cancel it
+ // the animation is no longer changing anything - cancel it
animatorToCancel = runningAnim;
break;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 8a18aaf..b0181bb 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -77,8 +77,8 @@ public interface WindowManager extends ViewManager {
implements Parcelable {
/**
* X position for this window. With the default gravity it is ignored.
- * When using {@link Gravity#LEFT} or {@link Gravity#RIGHT} it provides
- * an offset from the given edge.
+ * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
+ * {@link Gravity#END} it provides an offset from the given edge.
*/
@ViewDebug.ExportedProperty
public int x;
@@ -388,6 +388,12 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
/**
+ * Window type: Navigation bar (when distinct from status bar)
+ * @hide
+ */
+ public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 02ab1dc..54e7c04 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -23,7 +23,6 @@ import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.Log;
import android.util.Slog;
import android.view.WindowManager;
@@ -81,7 +80,7 @@ public class WindowManagerImpl implements WindowManager {
public static final int ADD_PERMISSION_DENIED = -8;
private View[] mViews;
- private ViewRoot[] mRoots;
+ private ViewAncestor[] mRoots;
private WindowManager.LayoutParams[] mParams;
private final static Object sLock = new Object();
@@ -193,13 +192,9 @@ public class WindowManagerImpl implements WindowManager {
addView(view, params, cih, false);
}
- public void addViewNesting(View view, ViewGroup.LayoutParams params) {
- addView(view, params, null, false);
- }
-
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
- if (Config.LOGV) Log.v("WindowManager", "addView view=" + view);
+ if (false) Log.v("WindowManager", "addView view=" + view);
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(
@@ -209,7 +204,7 @@ public class WindowManagerImpl implements WindowManager {
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams)params;
- ViewRoot root;
+ ViewAncestor root;
View panelParentView = null;
synchronized (this) {
@@ -246,7 +241,7 @@ public class WindowManagerImpl implements WindowManager {
}
}
- root = new ViewRoot(view.getContext());
+ root = new ViewAncestor(view.getContext());
root.mAddNesting = 1;
if (cih == null) {
root.mCompatibilityInfo = new CompatibilityInfoHolder();
@@ -259,7 +254,7 @@ public class WindowManagerImpl implements WindowManager {
if (mViews == null) {
index = 1;
mViews = new View[1];
- mRoots = new ViewRoot[1];
+ mRoots = new ViewAncestor[1];
mParams = new WindowManager.LayoutParams[1];
} else {
index = mViews.length + 1;
@@ -267,7 +262,7 @@ public class WindowManagerImpl implements WindowManager {
mViews = new View[index];
System.arraycopy(old, 0, mViews, 0, index-1);
old = mRoots;
- mRoots = new ViewRoot[index];
+ mRoots = new ViewAncestor[index];
System.arraycopy(old, 0, mRoots, 0, index-1);
old = mParams;
mParams = new WindowManager.LayoutParams[index];
@@ -295,7 +290,7 @@ public class WindowManagerImpl implements WindowManager {
synchronized (this) {
int index = findViewLocked(view, true);
- ViewRoot root = mRoots[index];
+ ViewAncestor root = mRoots[index];
mParams[index] = wparams;
root.setLayoutParams(wparams, false);
}
@@ -310,14 +305,14 @@ public class WindowManagerImpl implements WindowManager {
}
throw new IllegalStateException("Calling with view " + view
- + " but the ViewRoot is attached to " + curView);
+ + " but the ViewAncestor is attached to " + curView);
}
}
public void removeViewImmediate(View view) {
synchronized (this) {
int index = findViewLocked(view, true);
- ViewRoot root = mRoots[index];
+ ViewAncestor root = mRoots[index];
View curView = root.getView();
root.mAddNesting = 0;
@@ -328,12 +323,12 @@ public class WindowManagerImpl implements WindowManager {
}
throw new IllegalStateException("Calling with view " + view
- + " but the ViewRoot is attached to " + curView);
+ + " but the ViewAncestor is attached to " + curView);
}
}
View removeViewLocked(int index) {
- ViewRoot root = mRoots[index];
+ ViewAncestor root = mRoots[index];
View view = root.getView();
// Don't really remove until we have matched all calls to add().
@@ -361,7 +356,7 @@ public class WindowManagerImpl implements WindowManager {
removeItem(tmpViews, mViews, index);
mViews = tmpViews;
- ViewRoot[] tmpRoots = new ViewRoot[count-1];
+ ViewAncestor[] tmpRoots = new ViewAncestor[count-1];
removeItem(tmpRoots, mRoots, index);
mRoots = tmpRoots;
@@ -388,7 +383,7 @@ public class WindowManagerImpl implements WindowManager {
//Log.i("foo", "@ " + i + " token " + mParams[i].token
// + " view " + mRoots[i].getView());
if (token == null || mParams[i].token == token) {
- ViewRoot root = mRoots[i];
+ ViewAncestor root = mRoots[i];
root.mAddNesting = 1;
//Log.i("foo", "Force closing " + root);
@@ -415,7 +410,7 @@ public class WindowManagerImpl implements WindowManager {
int count = mViews.length;
for (int i=0; i<count; i++) {
if (token == null || mParams[i].token == token) {
- ViewRoot root = mRoots[i];
+ ViewAncestor root = mRoots[i];
root.setStopped(stopped);
}
}
@@ -427,7 +422,7 @@ public class WindowManagerImpl implements WindowManager {
int count = mViews.length;
config = new Configuration(config);
for (int i=0; i<count; i++) {
- ViewRoot root = mRoots[i];
+ ViewAncestor root = mRoots[i];
root.requestUpdateConfiguration(config);
}
}
@@ -435,13 +430,13 @@ public class WindowManagerImpl implements WindowManager {
public WindowManager.LayoutParams getRootViewLayoutParameter(View view) {
ViewParent vp = view.getParent();
- while (vp != null && !(vp instanceof ViewRoot)) {
+ while (vp != null && !(vp instanceof ViewAncestor)) {
vp = vp.getParent();
}
if (vp == null) return null;
- ViewRoot vr = (ViewRoot)vp;
+ ViewAncestor vr = (ViewAncestor)vp;
int N = mRoots.length;
for (int i = 0; i < N; ++i) {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 4d4569c..8a30c7b 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -82,6 +82,8 @@ public interface WindowManagerPolicy {
public final static int FLAG_INJECTED = 0x01000000;
public final static int FLAG_TRUSTED = 0x02000000;
+ public final static int FLAG_FILTERED = 0x04000000;
+ public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000;
public final static int FLAG_WOKE_HERE = 0x10000000;
public final static int FLAG_BRIGHT_HERE = 0x20000000;
@@ -889,7 +891,13 @@ public interface WindowManagerPolicy {
public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always);
/**
- * Called when we have stopped keeping the screen on because a window
+ * Called when we have started keeping the screen on because a window
+ * requesting this has become visible.
+ */
+ public void screenOnStartedLw();
+
+ /**
+ * Called when we have stopped keeping the screen on because the last window
* requesting this is no longer visible.
*/
public void screenOnStoppedLw();
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 62d3e6a..d128b57 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -21,7 +21,6 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.util.Config;
import android.util.Log;
import android.util.Slog;
@@ -47,7 +46,7 @@ import android.util.Slog;
public abstract class WindowOrientationListener {
private static final String TAG = "WindowOrientationListener";
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG || Config.DEBUG;
+ private static final boolean localLOGV = DEBUG || false;
private SensorManager mSensorManager;
private boolean mEnabled;
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index fc61700..06e4827 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -16,18 +16,34 @@
package android.view.accessibility;
+import android.accessibilityservice.IAccessibilityServiceConnection;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.text.TextUtils;
+import android.view.View;
import java.util.ArrayList;
-import java.util.List;
/**
* This class represents accessibility events that are sent by the system when
* something notable happens in the user interface. For example, when a
* {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc.
* <p>
+ * An accessibility event is fired by an individual view which populates the event with
+ * a record for its state and requests from its parent to send the event to interested
+ * parties. The parent can optionally add a record for itself before dispatching a similar
+ * request to its parent. A parent can also choose not to respect the request for sending
+ * an event. The accessibility event is sent by the topmost view in the view tree.
+ * Therefore, an {@link android.accessibilityservice.AccessibilityService} can explore
+ * all records in an accessibility event to obtain more information about the context
+ * in which the event was fired.
+ * <p>
+ * A client can add, remove, and modify records. The getters and setters for individual
+ * properties operate on the current record which can be explicitly set by the client. By
+ * default current is the first record. Thus, querying a record would require setting
+ * it as the current one and interacting with the property getters and setters.
+ * <p>
* This class represents various semantically different accessibility event
* types. Each event type has associated a set of related properties. In other
* words, each event type is characterized via a subset of the properties exposed
@@ -145,7 +161,8 @@ import java.util.List;
* @see android.view.accessibility.AccessibilityManager
* @see android.accessibilityservice.AccessibilityService
*/
-public final class AccessibilityEvent implements Parcelable {
+public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
+ private static final boolean DEBUG = false;
/**
* Invalid selection/focus position.
@@ -159,7 +176,12 @@ public final class AccessibilityEvent implements Parcelable {
*
* @see #getBeforeText()
* @see #getText()
+ * </br>
+ * Note: This constant is no longer needed since there
+ * is no limit on the length of text that is contained
+ * in an accessibility event anymore.
*/
+ @Deprecated
public static final int MAX_TEXT_LENGTH = 500;
/**
@@ -202,6 +224,26 @@ public final class AccessibilityEvent implements Parcelable {
public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
/**
+ * Represents the event of a hover enter over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
+
+ /**
+ * Represents the event of a hover exit over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
+
+ /**
+ * Represents the event of starting a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
+
+ /**
+ * Represents the event of ending a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
+
+ /**
* Mask for {@link AccessibilityEvent} all types.
*
* @see #TYPE_VIEW_CLICKED
@@ -214,116 +256,73 @@ public final class AccessibilityEvent implements Parcelable {
*/
public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
- private static final int MAX_POOL_SIZE = 2;
- private static final Object mPoolLock = new Object();
+ private static final int MAX_POOL_SIZE = 10;
+ private static final Object sPoolLock = new Object();
private static AccessibilityEvent sPool;
private static int sPoolSize;
-
- private static final int CHECKED = 0x00000001;
- private static final int ENABLED = 0x00000002;
- private static final int PASSWORD = 0x00000004;
- private static final int FULL_SCREEN = 0x00000080;
-
private AccessibilityEvent mNext;
+ private boolean mIsInPool;
private int mEventType;
- private int mBooleanProperties;
- private int mCurrentItemIndex;
- private int mItemCount;
- private int mFromIndex;
- private int mAddedCount;
- private int mRemovedCount;
-
- private long mEventTime;
-
- private CharSequence mClassName;
+ private int mSourceAccessibilityViewId = View.NO_ID;
+ private int mSourceAccessibilityWindowId = View.NO_ID;
private CharSequence mPackageName;
- private CharSequence mContentDescription;
- private CharSequence mBeforeText;
-
- private Parcelable mParcelableData;
+ private long mEventTime;
- private final List<CharSequence> mText = new ArrayList<CharSequence>();
+ private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
- private boolean mIsInPool;
+ private IAccessibilityServiceConnection mConnection;
/*
* Hide constructor from clients.
*/
private AccessibilityEvent() {
- mCurrentItemIndex = INVALID_POSITION;
}
/**
- * Gets if the source is checked.
+ * Initialize an event from another one.
*
- * @return True if the view is checked, false otherwise.
+ * @param event The event to initialize from.
*/
- public boolean isChecked() {
- return getBooleanProperty(CHECKED);
+ void init(AccessibilityEvent event) {
+ super.init(event);
+ mEventType = event.mEventType;
+ mEventTime = event.mEventTime;
+ mSourceAccessibilityWindowId = event.mSourceAccessibilityWindowId;
+ mSourceAccessibilityViewId = event.mSourceAccessibilityViewId;
+ mPackageName = event.mPackageName;
+ mConnection = event.mConnection;
}
/**
- * Sets if the source is checked.
+ * Gets the number of records contained in the event.
*
- * @param isChecked True if the view is checked, false otherwise.
+ * @return The number of records.
*/
- public void setChecked(boolean isChecked) {
- setBooleanProperty(CHECKED, isChecked);
+ public int getRecordCount() {
+ return mRecords.size();
}
/**
- * Gets if the source is enabled.
+ * Appends an {@link AccessibilityRecord} to the end of event records.
*
- * @return True if the view is enabled, false otherwise.
- */
- public boolean isEnabled() {
- return getBooleanProperty(ENABLED);
- }
-
- /**
- * Sets if the source is enabled.
- *
- * @param isEnabled True if the view is enabled, false otherwise.
- */
- public void setEnabled(boolean isEnabled) {
- setBooleanProperty(ENABLED, isEnabled);
- }
-
- /**
- * Gets if the source is a password field.
+ * @param record The record to append.
*
- * @return True if the view is a password field, false otherwise.
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
- public boolean isPassword() {
- return getBooleanProperty(PASSWORD);
+ public void appendRecord(AccessibilityRecord record) {
+ enforceNotSealed();
+ mRecords.add(record);
}
/**
- * Sets if the source is a password field.
+ * Gets the records at a given index.
*
- * @param isPassword True if the view is a password field, false otherwise.
+ * @param index The index.
+ * @return The records at the specified index.
*/
- public void setPassword(boolean isPassword) {
- setBooleanProperty(PASSWORD, isPassword);
- }
-
- /**
- * Sets if the source is taking the entire screen.
- *
- * @param isFullScreen True if the source is full screen, false otherwise.
- */
- public void setFullScreen(boolean isFullScreen) {
- setBooleanProperty(FULL_SCREEN, isFullScreen);
- }
-
- /**
- * Gets if the source is taking the entire screen.
- *
- * @return True if the source is full screen, false otherwise.
- */
- public boolean isFullScreen() {
- return getBooleanProperty(FULL_SCREEN);
+ public AccessibilityRecord getRecord(int index) {
+ return mRecords.get(index);
}
/**
@@ -336,102 +335,90 @@ public final class AccessibilityEvent implements Parcelable {
}
/**
- * Sets the event type.
+ * Sets the event source.
*
- * @param eventType The event type.
- */
- public void setEventType(int eventType) {
- mEventType = eventType;
- }
-
- /**
- * Gets the number of items that can be visited.
+ * @param source The source.
*
- * @return The number of items.
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
- public int getItemCount() {
- return mItemCount;
+ public void setSource(View source) {
+ enforceNotSealed();
+ if (source != null) {
+ mSourceAccessibilityWindowId = source.getAccessibilityWindowId();
+ mSourceAccessibilityViewId = source.getAccessibilityViewId();
+ } else {
+ mSourceAccessibilityWindowId = View.NO_ID;
+ mSourceAccessibilityViewId = View.NO_ID;
+ }
}
/**
- * Sets the number of items that can be visited.
- *
- * @param itemCount The number of items.
- */
- public void setItemCount(int itemCount) {
- mItemCount = itemCount;
+ * Gets the {@link AccessibilityNodeInfo} of the event source.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ * @return The info.
+ */
+ public AccessibilityNodeInfo getSource() {
+ enforceSealed();
+ if (mSourceAccessibilityWindowId == View.NO_ID
+ || mSourceAccessibilityViewId == View.NO_ID) {
+ return null;
+ }
+ try {
+ return mConnection.findAccessibilityNodeInfoByAccessibilityId(
+ mSourceAccessibilityWindowId, mSourceAccessibilityViewId);
+ } catch (RemoteException e) {
+ return null;
+ }
}
/**
- * Gets the index of the source in the list of items the can be visited.
+ * Gets the id of the window from which the event comes from.
*
- * @return The current item index.
+ * @return The window id.
*/
- public int getCurrentItemIndex() {
- return mCurrentItemIndex;
+ public int getAccessibilityWindowId() {
+ return mSourceAccessibilityWindowId;
}
/**
- * Sets the index of the source in the list of items that can be visited.
+ * Sets the client token for the accessibility service that
+ * provided this node info.
*
- * @param currentItemIndex The current item index.
- */
- public void setCurrentItemIndex(int currentItemIndex) {
- mCurrentItemIndex = currentItemIndex;
- }
-
- /**
- * Gets the index of the first character of the changed sequence.
+ * @param connection The connection.
*
- * @return The index of the first character.
+ * @hide
*/
- public int getFromIndex() {
- return mFromIndex;
+ public final void setConnection(IAccessibilityServiceConnection connection) {
+ mConnection = connection;
}
/**
- * Sets the index of the first character of the changed sequence.
+ * Gets the accessibility window id of the source window.
*
- * @param fromIndex The index of the first character.
- */
- public void setFromIndex(int fromIndex) {
- mFromIndex = fromIndex;
- }
-
- /**
- * Gets the number of added characters.
+ * @return The id.
*
- * @return The number of added characters.
+ * @hide
*/
- public int getAddedCount() {
- return mAddedCount;
+ public int getSourceAccessibilityWindowId() {
+ return mSourceAccessibilityWindowId;
}
/**
- * Sets the number of added characters.
- *
- * @param addedCount The number of added characters.
- */
- public void setAddedCount(int addedCount) {
- mAddedCount = addedCount;
- }
-
- /**
- * Gets the number of removed characters.
+ * Sets the event type.
*
- * @return The number of removed characters.
- */
- public int getRemovedCount() {
- return mRemovedCount;
- }
-
- /**
- * Sets the number of removed characters.
+ * @param eventType The event type.
*
- * @param removedCount The number of removed characters.
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
- public void setRemovedCount(int removedCount) {
- mRemovedCount = removedCount;
+ public void setEventType(int eventType) {
+ enforceNotSealed();
+ mEventType = eventType;
}
/**
@@ -447,30 +434,15 @@ public final class AccessibilityEvent implements Parcelable {
* Sets the time in which this event was sent.
*
* @param eventTime The event time.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setEventTime(long eventTime) {
+ enforceNotSealed();
mEventTime = eventTime;
}
/**
- * Gets the class name of the source.
- *
- * @return The class name.
- */
- public CharSequence getClassName() {
- return mClassName;
- }
-
- /**
- * Sets the class name of the source.
- *
- * @param className The lass name.
- */
- public void setClassName(CharSequence className) {
- mClassName = className;
- }
-
- /**
* Gets the package name of the source.
*
* @return The package name.
@@ -483,76 +455,15 @@ public final class AccessibilityEvent implements Parcelable {
* Sets the package name of the source.
*
* @param packageName The package name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
*/
public void setPackageName(CharSequence packageName) {
+ enforceNotSealed();
mPackageName = packageName;
}
/**
- * Gets the text of the event. The index in the list represents the priority
- * of the text. Specifically, the lower the index the higher the priority.
- *
- * @return The text.
- */
- public List<CharSequence> getText() {
- return mText;
- }
-
- /**
- * Sets the text before a change.
- *
- * @return The text before the change.
- */
- public CharSequence getBeforeText() {
- return mBeforeText;
- }
-
- /**
- * Sets the text before a change.
- *
- * @param beforeText The text before the change.
- */
- public void setBeforeText(CharSequence beforeText) {
- mBeforeText = beforeText;
- }
-
- /**
- * Gets the description of the source.
- *
- * @return The description.
- */
- public CharSequence getContentDescription() {
- return mContentDescription;
- }
-
- /**
- * Sets the description of the source.
- *
- * @param contentDescription The description.
- */
- public void setContentDescription(CharSequence contentDescription) {
- mContentDescription = contentDescription;
- }
-
- /**
- * Gets the {@link Parcelable} data.
- *
- * @return The parcelable data.
- */
- public Parcelable getParcelableData() {
- return mParcelableData;
- }
-
- /**
- * Sets the {@link Parcelable} data of the event.
- *
- * @param parcelableData The parcelable data.
- */
- public void setParcelableData(Parcelable parcelableData) {
- mParcelableData = parcelableData;
- }
-
- /**
* Returns a cached instance if such is available or a new one is
* instantiated with type property set.
*
@@ -567,12 +478,33 @@ public final class AccessibilityEvent implements Parcelable {
/**
* Returns a cached instance if such is available or a new one is
+ * instantiated with type property set.
+ *
+ * @param event The other event.
+ * @return An instance.
+ */
+ public static AccessibilityEvent obtain(AccessibilityEvent event) {
+ AccessibilityEvent eventClone = AccessibilityEvent.obtain();
+ eventClone.init(event);
+
+ final int recordCount = event.mRecords.size();
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = event.mRecords.get(i);
+ AccessibilityRecord recordClone = AccessibilityRecord.obtain(record);
+ eventClone.mRecords.add(recordClone);
+ }
+
+ return eventClone;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
* instantiated.
*
* @return An instance.
*/
public static AccessibilityEvent obtain() {
- synchronized (mPoolLock) {
+ synchronized (sPoolLock) {
if (sPool != null) {
AccessibilityEvent event = sPool;
sPool = sPool.mNext;
@@ -589,14 +521,16 @@ public final class AccessibilityEvent implements Parcelable {
* Return an instance back to be reused.
* <p>
* <b>Note: You must not touch the object after calling this function.</b>
+ *
+ * @throws IllegalStateException If the event is already recycled.
*/
+ @Override
public void recycle() {
if (mIsInPool) {
- return;
+ throw new IllegalStateException("Event already recycled!");
}
-
clear();
- synchronized (mPoolLock) {
+ synchronized (sPoolLock) {
if (sPoolSize <= MAX_POOL_SIZE) {
mNext = sPool;
sPool = this;
@@ -608,87 +542,123 @@ public final class AccessibilityEvent implements Parcelable {
/**
* Clears the state of this instance.
+ *
+ * @hide
*/
- private void clear() {
+ @Override
+ protected void clear() {
+ super.clear();
+ mConnection = null;
mEventType = 0;
- mBooleanProperties = 0;
- mCurrentItemIndex = INVALID_POSITION;
- mItemCount = 0;
- mFromIndex = 0;
- mAddedCount = 0;
- mRemovedCount = 0;
- mEventTime = 0;
- mClassName = null;
+ mSourceAccessibilityViewId = View.NO_ID;
+ mSourceAccessibilityWindowId = View.NO_ID;
mPackageName = null;
- mContentDescription = null;
- mBeforeText = null;
- mParcelableData = null;
- mText.clear();
+ mEventTime = 0;
+ while (!mRecords.isEmpty()) {
+ AccessibilityRecord record = mRecords.remove(0);
+ record.recycle();
+ }
}
/**
- * Gets the value of a boolean property.
+ * Creates a new instance from a {@link Parcel}.
*
- * @param property The property.
- * @return The value.
+ * @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
*/
- private boolean getBooleanProperty(int property) {
- return (mBooleanProperties & property) == property;
+ public void initFromParcel(Parcel parcel) {
+ if (parcel.readInt() == 1) {
+ mConnection = IAccessibilityServiceConnection.Stub.asInterface(
+ parcel.readStrongBinder());
+ }
+ setSealed(parcel.readInt() == 1);
+ mEventType = parcel.readInt();
+ mSourceAccessibilityWindowId = parcel.readInt();
+ mSourceAccessibilityViewId = parcel.readInt();
+ mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mEventTime = parcel.readLong();
+ readAccessibilityRecordFromParcel(this, parcel);
+
+ // Read the records.
+ final int recordCount = parcel.readInt();
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = AccessibilityRecord.obtain();
+ readAccessibilityRecordFromParcel(record, parcel);
+ mRecords.add(record);
+ }
}
/**
- * Sets a boolean property.
+ * Reads an {@link AccessibilityRecord} from a parcel.
*
- * @param property The property.
- * @param value The value.
+ * @param record The record to initialize.
+ * @param parcel The parcel to read from.
*/
- private void setBooleanProperty(int property, boolean value) {
- if (value) {
- mBooleanProperties |= property;
- } else {
- mBooleanProperties &= ~property;
- }
+ private void readAccessibilityRecordFromParcel(AccessibilityRecord record,
+ Parcel parcel) {
+ record.mBooleanProperties = parcel.readInt();
+ record.mCurrentItemIndex = parcel.readInt();
+ record.mItemCount = parcel.readInt();
+ record.mFromIndex = parcel.readInt();
+ record.mAddedCount = parcel.readInt();
+ record.mRemovedCount = parcel.readInt();
+ record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mParcelableData = parcel.readParcelable(null);
+ parcel.readList(record.mText, null);
}
/**
- * Creates a new instance from a {@link Parcel}.
- *
- * @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
+ * {@inheritDoc}
*/
- public void initFromParcel(Parcel parcel) {
- mEventType = parcel.readInt();
- mBooleanProperties = parcel.readInt();
- mCurrentItemIndex = parcel.readInt();
- mItemCount = parcel.readInt();
- mFromIndex = parcel.readInt();
- mAddedCount = parcel.readInt();
- mRemovedCount = parcel.readInt();
- mEventTime = parcel.readLong();
- mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mParcelableData = parcel.readParcelable(null);
- parcel.readList(mText, null);
- }
-
public void writeToParcel(Parcel parcel, int flags) {
+ if (mConnection == null) {
+ parcel.writeInt(0);
+ } else {
+ parcel.writeInt(1);
+ parcel.writeStrongBinder(mConnection.asBinder());
+ }
+ parcel.writeInt(isSealed() ? 1 : 0);
parcel.writeInt(mEventType);
- parcel.writeInt(mBooleanProperties);
- parcel.writeInt(mCurrentItemIndex);
- parcel.writeInt(mItemCount);
- parcel.writeInt(mFromIndex);
- parcel.writeInt(mAddedCount);
- parcel.writeInt(mRemovedCount);
- parcel.writeLong(mEventTime);
- TextUtils.writeToParcel(mClassName, parcel, 0);
+ parcel.writeInt(mSourceAccessibilityWindowId);
+ parcel.writeInt(mSourceAccessibilityViewId);
TextUtils.writeToParcel(mPackageName, parcel, 0);
- TextUtils.writeToParcel(mContentDescription, parcel, 0);
- TextUtils.writeToParcel(mBeforeText, parcel, 0);
- parcel.writeParcelable(mParcelableData, flags);
- parcel.writeList(mText);
+ parcel.writeLong(mEventTime);
+ writeAccessibilityRecordToParcel(this, parcel, flags);
+
+ // Write the records.
+ final int recordCount = getRecordCount();
+ parcel.writeInt(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ writeAccessibilityRecordToParcel(record, parcel, flags);
+ }
+ }
+
+ /**
+ * Writes an {@link AccessibilityRecord} to a parcel.
+ *
+ * @param record The record to write.
+ * @param parcel The parcel to which to write.
+ */
+ private void writeAccessibilityRecordToParcel(AccessibilityRecord record, Parcel parcel,
+ int flags) {
+ parcel.writeInt(record.mBooleanProperties);
+ parcel.writeInt(record.mCurrentItemIndex);
+ parcel.writeInt(record.mItemCount);
+ parcel.writeInt(record.mFromIndex);
+ parcel.writeInt(record.mAddedCount);
+ parcel.writeInt(record.mRemovedCount);
+ TextUtils.writeToParcel(record.mClassName, parcel, flags);
+ TextUtils.writeToParcel(record.mContentDescription, parcel, flags);
+ TextUtils.writeToParcel(record.mBeforeText, parcel, flags);
+ parcel.writeParcelable(record.mParcelableData, flags);
+ parcel.writeList(record.mText);
}
+ /**
+ * {@inheritDoc}
+ */
public int describeContents() {
return 0;
}
@@ -696,28 +666,79 @@ public final class AccessibilityEvent implements Parcelable {
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
+ builder.append("; EventType: ").append(eventTypeToString(mEventType));
+ builder.append("; EventTime: ").append(mEventTime);
+ builder.append("; PackageName: ").append(mPackageName);
builder.append(super.toString());
- builder.append("; EventType: " + mEventType);
- builder.append("; EventTime: " + mEventTime);
- builder.append("; ClassName: " + mClassName);
- builder.append("; PackageName: " + mPackageName);
- builder.append("; Text: " + mText);
- builder.append("; ContentDescription: " + mContentDescription);
- builder.append("; ItemCount: " + mItemCount);
- builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
- builder.append("; IsEnabled: " + isEnabled());
- builder.append("; IsPassword: " + isPassword());
- builder.append("; IsChecked: " + isChecked());
- builder.append("; IsFullScreen: " + isFullScreen());
- builder.append("; BeforeText: " + mBeforeText);
- builder.append("; FromIndex: " + mFromIndex);
- builder.append("; AddedCount: " + mAddedCount);
- builder.append("; RemovedCount: " + mRemovedCount);
- builder.append("; ParcelableData: " + mParcelableData);
+ if (DEBUG) {
+ builder.append("\n");
+ builder.append("; sourceAccessibilityWindowId: ").append(mSourceAccessibilityWindowId);
+ builder.append("; sourceAccessibilityViewId: ").append(mSourceAccessibilityViewId);
+ for (int i = 0; i < mRecords.size(); i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ builder.append(" Record ");
+ builder.append(i);
+ builder.append(":");
+ builder.append(" [ ClassName: " + record.mClassName);
+ builder.append("; Text: " + record.mText);
+ builder.append("; ContentDescription: " + record.mContentDescription);
+ builder.append("; ItemCount: " + record.mItemCount);
+ builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex);
+ builder.append("; IsEnabled: " + record.isEnabled());
+ builder.append("; IsPassword: " + record.isPassword());
+ builder.append("; IsChecked: " + record.isChecked());
+ builder.append("; IsFullScreen: " + record.isFullScreen());
+ builder.append("; BeforeText: " + record.mBeforeText);
+ builder.append("; FromIndex: " + record.mFromIndex);
+ builder.append("; AddedCount: " + record.mAddedCount);
+ builder.append("; RemovedCount: " + record.mRemovedCount);
+ builder.append("; ParcelableData: " + record.mParcelableData);
+ builder.append(" ]");
+ builder.append("\n");
+ }
+ } else {
+ builder.append("; recordCount: ").append(getAddedCount());
+ }
return builder.toString();
}
/**
+ * Returns the string representation of an event type. For example,
+ * {@link #TYPE_VIEW_CLICKED} is represented by the string TYPE_VIEW_CLICKED.
+ *
+ * @param feedbackType The event type
+ * @return The string representation.
+ */
+ public static String eventTypeToString(int feedbackType) {
+ switch (feedbackType) {
+ 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";
+ default:
+ return null;
+ }
+ }
+
+ /**
* @see Parcelable.Creator
*/
public static final Parcelable.Creator<AccessibilityEvent> CREATOR =
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index f406da9..eece64a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -16,8 +16,7 @@
package android.view.accessibility;
-import static android.util.Config.LOGV;
-
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.Binder;
@@ -29,9 +28,13 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
+import android.view.IWindow;
+import android.view.View;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
@@ -46,6 +49,8 @@ import java.util.List;
* @see android.content.Context#getSystemService
*/
public final class AccessibilityManager {
+ private static final boolean DEBUG = false;
+
private static final String LOG_TAG = "AccessibilityManager";
static final Object sInstanceSync = new Object();
@@ -60,6 +65,21 @@ public final class AccessibilityManager {
boolean mIsEnabled;
+ final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners =
+ new CopyOnWriteArrayList<AccessibilityStateChangeListener>();
+
+ /**
+ * Listener for the accessibility state.
+ */
+ public interface AccessibilityStateChangeListener {
+ /**
+ * Called back on change in the accessibility state.
+ *
+ * @param enabled
+ */
+ public void onAccessibilityStateChanged(boolean enabled);
+ }
+
final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
public void setEnabled(boolean enabled) {
mHandler.obtainMessage(DO_SET_ENABLED, enabled ? 1 : 0, 0).sendToTarget();
@@ -76,9 +96,8 @@ public final class AccessibilityManager {
public void handleMessage(Message message) {
switch (message.what) {
case DO_SET_ENABLED :
- synchronized (mHandler) {
- mIsEnabled = (message.arg1 == 1);
- }
+ final boolean isEnabled = (message.arg1 == 1);
+ setAccessibilityState(isEnabled);
return;
default :
Log.w(LOG_TAG, "Unknown message type: " + message.what);
@@ -115,7 +134,8 @@ public final class AccessibilityManager {
mService = service;
try {
- mIsEnabled = mService.addClient(mClient);
+ final boolean isEnabled = mService.addClient(mClient);
+ setAccessibilityState(isEnabled);
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
}
@@ -166,7 +186,7 @@ public final class AccessibilityManager {
long identityToken = Binder.clearCallingIdentity();
doRecycle = mService.sendAccessibilityEvent(event);
Binder.restoreCallingIdentity(identityToken);
- if (LOGV) {
+ if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
}
} catch (RemoteException re) {
@@ -187,7 +207,7 @@ public final class AccessibilityManager {
}
try {
mService.interrupt();
- if (LOGV) {
+ if (DEBUG) {
Log.i(LOG_TAG, "Requested interrupt from all services");
}
} catch (RemoteException re) {
@@ -199,12 +219,51 @@ public final class AccessibilityManager {
* Returns the {@link ServiceInfo}s of the installed accessibility services.
*
* @return An unmodifiable list with {@link ServiceInfo}s.
+ *
+ * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
*/
+ @Deprecated
public List<ServiceInfo> getAccessibilityServiceList() {
- List<ServiceInfo> services = null;
+ List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
+ List<ServiceInfo> services = new ArrayList<ServiceInfo>();
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityServiceInfo info = infos.get(i);
+ services.add(info.getResolveInfo().serviceInfo);
+ }
+ return Collections.unmodifiableList(services);
+ }
+
+ /**
+ * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ */
+ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
+ List<AccessibilityServiceInfo> services = null;
+ try {
+ services = mService.getInstalledAccessibilityServiceList();
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ return Collections.unmodifiableList(services);
+ }
+
+ /**
+ * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
+ * for a given feedback type.
+ *
+ * @param feedbackType The feedback type (can be bitwise or of multiple types).
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ */
+ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
+ List<AccessibilityServiceInfo> services = null;
try {
- services = mService.getAccessibilityServiceList();
- if (LOGV) {
+ services = mService.getEnabledAccessibilityServiceList(feedbackType);
+ if (DEBUG) {
Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
}
} catch (RemoteException re) {
@@ -212,4 +271,81 @@ public final class AccessibilityManager {
}
return Collections.unmodifiableList(services);
}
+
+ /**
+ * Registers an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if successfully registered.
+ */
+ public boolean addAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return mAccessibilityStateChangeListeners.add(listener);
+ }
+
+ /**
+ * Unregisters an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if successfully unregistered.
+ */
+ public boolean removeAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return mAccessibilityStateChangeListeners.remove(listener);
+ }
+
+ /**
+ * Sets the enabled state.
+ *
+ * @param isEnabled The accessibility state.
+ */
+ private void setAccessibilityState(boolean isEnabled) {
+ synchronized (mHandler) {
+ if (isEnabled != mIsEnabled) {
+ mIsEnabled = isEnabled;
+ notifyAccessibilityStateChanged();
+ }
+ }
+ }
+
+ /**
+ * Notifies the registered {@link AccessibilityStateChangeListener}s.
+ */
+ private void notifyAccessibilityStateChanged() {
+ final int listenerCount = mAccessibilityStateChangeListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled);
+ }
+ }
+
+ /**
+ * Adds an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is added.
+ * @param connection The connection.
+ *
+ * @hide
+ */
+ public int addAccessibilityInteractionConnection(IWindow windowToken,
+ IAccessibilityInteractionConnection connection) {
+ try {
+ return mService.addAccessibilityInteractionConnection(windowToken, connection);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
+ }
+ return View.NO_ID;
+ }
+
+ /**
+ * Removed an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is removed.
+ *
+ * @hide
+ */
+ public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+ try {
+ mService.removeAccessibilityInteractionConnection(windowToken);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl b/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl
new file mode 100644
index 0000000..59175ce
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+parcelable AccessibilityNodeInfo;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
new file mode 100644
index 0000000..5fa65b4
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -0,0 +1,969 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.View;
+
+/**
+ * This class represents a node of the screen content. From the point of
+ * view of an accessibility service the screen content is presented as tree
+ * of accessibility nodes.
+ *
+ * TODO(svertoslavganov): Update the documentation, add sample, and describe
+ * the security policy.
+ */
+public class AccessibilityNodeInfo implements Parcelable {
+
+ private static final boolean DEBUG = false;
+
+ // Actions.
+
+ /**
+ * Action that focuses the node.
+ */
+ public static final int ACTION_FOCUS = 0x00000001;
+
+ /**
+ * Action that unfocuses the node.
+ */
+ public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+
+ /**
+ * Action that selects the node.
+ */
+ public static final int ACTION_SELECT = 0x00000004;
+
+ /**
+ * Action that unselects the node.
+ */
+ public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+
+ // Boolean attributes.
+
+ private static final int PROPERTY_CHECKABLE = 0x00000001;
+
+ private static final int PROPERTY_CHECKED = 0x00000002;
+
+ private static final int PROPERTY_FOCUSABLE = 0x00000004;
+
+ private static final int PROPERTY_FOCUSED = 0x00000008;
+
+ private static final int PROPERTY_SELECTED = 0x00000010;
+
+ private static final int PROPERTY_CLICKABLE = 0x00000020;
+
+ private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
+
+ private static final int PROPERTY_ENABLED = 0x00000080;
+
+ private static final int PROPERTY_PASSWORD = 0x00000100;
+
+ // Readable representations - lazily initialized.
+ private static SparseArray<String> sActionSymbolicNames;
+
+ // 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 boolean mSealed;
+
+ // Data.
+ private int mAccessibilityViewId = View.NO_ID;
+ private int mAccessibilityWindowId = View.NO_ID;
+ private int mParentAccessibilityViewId = View.NO_ID;
+ private int mBooleanProperties;
+ private final Rect mBounds = new Rect();
+
+ private CharSequence mPackageName;
+ private CharSequence mClassName;
+ private CharSequence mText;
+ private CharSequence mContentDescription;
+
+ private final SparseIntArray mChildAccessibilityIds = new SparseIntArray();
+ private int mActions;
+
+ private IAccessibilityServiceConnection mConnection;
+
+ /**
+ * Hide constructor from clients.
+ */
+ private AccessibilityNodeInfo() {
+ /* do nothing */
+ }
+
+ /**
+ * Sets the source.
+ *
+ * @param source The info source.
+ */
+ public void setSource(View source) {
+ enforceNotSealed();
+ mAccessibilityViewId = source.getAccessibilityViewId();
+ mAccessibilityWindowId = source.getAccessibilityWindowId();
+ }
+
+ /**
+ * Gets the id of the window from which the info comes from.
+ *
+ * @return The window id.
+ */
+ public int getAccessibilityWindowId() {
+ return mAccessibilityWindowId;
+ }
+
+ /**
+ * Gets the number of children.
+ *
+ * @return The child count.
+ */
+ public int getChildCount() {
+ return mChildAccessibilityIds.size();
+ }
+
+ /**
+ * Get the child at given index.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ * @param index The child index.
+ * @return The child node.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ *
+ */
+ public AccessibilityNodeInfo getChild(int index) {
+ enforceSealed();
+ final int childAccessibilityViewId = mChildAccessibilityIds.get(index);
+ try {
+ return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
+ childAccessibilityViewId);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Adds a child.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param child The child.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addChild(View child) {
+ enforceNotSealed();
+ final int childAccessibilityViewId = child.getAccessibilityViewId();
+ final int index = mChildAccessibilityIds.size();
+ mChildAccessibilityIds.put(index, childAccessibilityViewId);
+ }
+
+ /**
+ * Gets the actions that can be performed on the node.
+ *
+ * @return The bit mask of with actions.
+ *
+ * @see AccessibilityNodeInfo#ACTION_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_SELECT
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
+ */
+ public int getActions() {
+ return mActions;
+ }
+
+ /**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param action The action.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addAction(int action) {
+ enforceNotSealed();
+ mActions |= action;
+ }
+
+ /**
+ * Performs an action on the node.
+ * <p>
+ * Note: An action can be performed only if the request is made
+ * from an {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ * @param action The action to perform.
+ * @return True if the action was performed.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ */
+ public boolean performAction(int action) {
+ enforceSealed();
+ try {
+ return mConnection.performAccessibilityAction(mAccessibilityWindowId,
+ mAccessibilityViewId, action);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the unique id identifying this node's parent.
+ * <p>
+ * <strong>
+ * It is a client responsibility to recycle the received info by
+ * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
+ * of multiple instances.
+ * </strong>
+ * </p>
+ * @return The node's patent id.
+ */
+ public AccessibilityNodeInfo getParent() {
+ enforceSealed();
+ try {
+ return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
+ mParentAccessibilityViewId);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the parent.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param parent The parent.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setParent(View parent) {
+ enforceNotSealed();
+ mParentAccessibilityViewId = parent.getAccessibilityViewId();
+ }
+
+ /**
+ * Gets the node bounds in parent coordinates.
+ *
+ * @param outBounds The output node bounds.
+ */
+ public void getBounds(Rect outBounds) {
+ outBounds.set(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
+ }
+
+ /**
+ * Sets the node bounds in parent coordinates.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param bounds The node bounds.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBounds(Rect bounds) {
+ enforceNotSealed();
+ mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Gets whether this node is checkable.
+ *
+ * @return True if the node is checkable.
+ */
+ public boolean isCheckable() {
+ return getBooleanProperty(PROPERTY_CHECKABLE);
+ }
+
+ /**
+ * Sets whether this node is checkable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param checkable True if the node is checkable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setCheckable(boolean checkable) {
+ setBooleanProperty(PROPERTY_CHECKABLE, checkable);
+ }
+
+ /**
+ * Gets whether this node is checked.
+ *
+ * @return True if the node is checked.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets whether this node is checked.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param checked True if the node is checked.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setChecked(boolean checked) {
+ setBooleanProperty(PROPERTY_CHECKED, checked);
+ }
+
+ /**
+ * Gets whether this node is focusable.
+ *
+ * @return True if the node is focusable.
+ */
+ public boolean isFocusable() {
+ return getBooleanProperty(PROPERTY_FOCUSABLE);
+ }
+
+ /**
+ * Sets whether this node is focusable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param focusable True if the node is focusable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocusable(boolean focusable) {
+ setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
+ }
+
+ /**
+ * Gets whether this node is focused.
+ *
+ * @return True if the node is focused.
+ */
+ public boolean isFocused() {
+ return getBooleanProperty(PROPERTY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is focused.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param focused True if the node is focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocused(boolean focused) {
+ setBooleanProperty(PROPERTY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets whether this node is selected.
+ *
+ * @return True if the node is selected.
+ */
+ public boolean isSelected() {
+ return getBooleanProperty(PROPERTY_SELECTED);
+ }
+
+ /**
+ * Sets whether this node is selected.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param selected True if the node is selected.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setSelected(boolean selected) {
+ setBooleanProperty(PROPERTY_SELECTED, selected);
+ }
+
+ /**
+ * Gets whether this node is clickable.
+ *
+ * @return True if the node is clickable.
+ */
+ public boolean isClickable() {
+ return getBooleanProperty(PROPERTY_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is clickable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param clickable True if the node is clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClickable(boolean clickable) {
+ setBooleanProperty(PROPERTY_CLICKABLE, clickable);
+ }
+
+ /**
+ * Gets whether this node is long clickable.
+ *
+ * @return True if the node is long clickable.
+ */
+ public boolean isLongClickable() {
+ return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is long clickable.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param longClickable True if the node is long clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setLongClickable(boolean longClickable) {
+ setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
+ }
+
+ /**
+ * Gets whether this node is enabled.
+ *
+ * @return True if the node is enabled.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets whether this node is enabled.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param enabled True if the node is enabled.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEnabled(boolean enabled) {
+ setBooleanProperty(PROPERTY_ENABLED, enabled);
+ }
+
+ /**
+ * Gets whether this node is a password.
+ *
+ * @return True if the node is a password.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets whether this node is a password.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param password True if the node is a password.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPassword(boolean password) {
+ setBooleanProperty(PROPERTY_PASSWORD, password);
+ }
+
+ /**
+ * Gets the package this node comes from.
+ *
+ * @return The package name.
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sets the package this node comes from.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param packageName The package name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPackageName(CharSequence packageName) {
+ enforceNotSealed();
+ mPackageName = packageName;
+ }
+
+ /**
+ * Gets the class this node comes from.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class this node comes from.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param className The class name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClassName(CharSequence className) {
+ enforceNotSealed();
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of this node.
+ *
+ * @return The text.
+ */
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the text of this node.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param text The text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setText(CharSequence text) {
+ enforceNotSealed();
+ mText = text;
+ }
+
+ /**
+ * Gets the content description of this node.
+ *
+ * @return The content description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the content description of this node.
+ * <p>
+ * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param contentDescription The content description.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ enforceNotSealed();
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) != 0;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ enforceNotSealed();
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Sets the connection for interacting with the system.
+ *
+ * @param connection The client token.
+ *
+ * @hide
+ */
+ public final void setConnection(IAccessibilityServiceConnection connection) {
+ mConnection = connection;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ public void setSealed(boolean sealed) {
+ mSealed = sealed;
+ }
+
+ /**
+ * Gets if this instance is sealed.
+ *
+ * @return Whether is sealed.
+ *
+ * @hide
+ */
+ public boolean isSealed() {
+ return mSealed;
+ }
+
+ /**
+ * Enforces that this instance is sealed.
+ *
+ * @throws IllegalStateException If this instance is not sealed.
+ *
+ * @hide
+ */
+ protected void enforceSealed() {
+ if (!isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a not sealed instance.");
+ }
+ }
+
+ /**
+ * Enforces that this instance is not sealed.
+ *
+ * @throws IllegalStateException If this instance is sealed.
+ *
+ * @hide
+ */
+ protected void enforceNotSealed() {
+ if (isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on an sealed instance.");
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one
+ * and sets the source.
+ *
+ * @return An instance.
+ *
+ * @see #setSource(View)
+ */
+ public static AccessibilityNodeInfo obtain(View source) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setSource(source);
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one.
+ *
+ * @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();
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <b>Note: You must not touch the object after calling this function.</b>
+ *
+ * @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++;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <b>Note: After the instance is written to a parcel it is recycled.
+ * You must not touch the object after calling this function.</b>
+ * </p>
+ */
+ public void writeToParcel(Parcel parcel, int flags) {
+ if (mConnection == null) {
+ parcel.writeInt(0);
+ } else {
+ parcel.writeInt(1);
+ parcel.writeStrongBinder(mConnection.asBinder());
+ }
+ parcel.writeInt(isSealed() ? 1 : 0);
+ parcel.writeInt(mAccessibilityViewId);
+ parcel.writeInt(mAccessibilityWindowId);
+ parcel.writeInt(mParentAccessibilityViewId);
+
+ SparseIntArray childIds = mChildAccessibilityIds;
+ final int childIdsSize = childIds.size();
+ parcel.writeInt(childIdsSize);
+ for (int i = 0; i < childIdsSize; i++) {
+ parcel.writeInt(childIds.valueAt(i));
+ }
+
+ parcel.writeInt(mBounds.top);
+ parcel.writeInt(mBounds.bottom);
+ parcel.writeInt(mBounds.left);
+ parcel.writeInt(mBounds.right);
+
+ parcel.writeInt(mActions);
+
+ parcel.writeInt(mBooleanProperties);
+
+ TextUtils.writeToParcel(mPackageName, parcel, flags);
+ TextUtils.writeToParcel(mClassName, parcel, flags);
+ TextUtils.writeToParcel(mText, parcel, flags);
+ TextUtils.writeToParcel(mContentDescription, parcel, flags);
+
+ // Since instances of this class are fetched via synchronous i.e. blocking
+ // calls in IPCs and we always recycle as soon as the instance is marshaled.
+ recycle();
+ }
+
+ /**
+ * Creates a new instance from a {@link Parcel}.
+ *
+ * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
+ */
+ private void initFromParcel(Parcel parcel) {
+ if (parcel.readInt() == 1) {
+ mConnection = IAccessibilityServiceConnection.Stub.asInterface(
+ parcel.readStrongBinder());
+ }
+ mSealed = (parcel.readInt() == 1);
+ mAccessibilityViewId = parcel.readInt();
+ mAccessibilityWindowId = parcel.readInt();
+ mParentAccessibilityViewId = parcel.readInt();
+
+ SparseIntArray childIds = mChildAccessibilityIds;
+ final int childrenSize = parcel.readInt();
+ for (int i = 0; i < childrenSize; i++) {
+ final int childId = parcel.readInt();
+ childIds.put(i, childId);
+ }
+
+ mBounds.top = parcel.readInt();
+ mBounds.bottom = parcel.readInt();
+ mBounds.left = parcel.readInt();
+ mBounds.right = parcel.readInt();
+
+ mActions = parcel.readInt();
+
+ mBooleanProperties = parcel.readInt();
+
+ mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ private void clear() {
+ mSealed = false;
+ mConnection = null;
+ mAccessibilityViewId = View.NO_ID;
+ mParentAccessibilityViewId = View.NO_ID;
+ mChildAccessibilityIds.clear();
+ mBounds.set(0, 0, 0, 0);
+ mBooleanProperties = 0;
+ mPackageName = null;
+ mClassName = null;
+ mText = null;
+ mContentDescription = null;
+ mActions = 0;
+ }
+
+ /**
+ * Gets the human readable action symbolic name.
+ *
+ * @param action The action.
+ * @return The symbolic name.
+ */
+ private static String getActionSymbolicName(int action) {
+ SparseArray<String> actionSymbolicNames = sActionSymbolicNames;
+ if (actionSymbolicNames == null) {
+ actionSymbolicNames = sActionSymbolicNames = new SparseArray<String>();
+ actionSymbolicNames.put(ACTION_FOCUS, "ACTION_FOCUS");
+ actionSymbolicNames.put(ACTION_CLEAR_FOCUS, "ACTION_UNFOCUS");
+ actionSymbolicNames.put(ACTION_SELECT, "ACTION_SELECT");
+ actionSymbolicNames.put(ACTION_CLEAR_SELECTION, "ACTION_UNSELECT");
+ }
+ return actionSymbolicNames.get(action);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null) {
+ return false;
+ }
+ if (getClass() != object.getClass()) {
+ return false;
+ }
+ AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
+ if (mAccessibilityViewId != other.mAccessibilityViewId) {
+ return false;
+ }
+ if (mAccessibilityWindowId != other.mAccessibilityWindowId) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mAccessibilityViewId;
+ result = prime * result + mAccessibilityWindowId;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(super.toString());
+
+ if (DEBUG) {
+ builder.append("; accessibilityId: " + mAccessibilityViewId);
+ builder.append("; parentAccessibilityId: " + mParentAccessibilityViewId);
+ SparseIntArray childIds = mChildAccessibilityIds;
+ builder.append("; childAccessibilityIds: [");
+ for (int i = 0, count = childIds.size(); i < count; i++) {
+ builder.append(childIds.valueAt(i));
+ if (i < count - 1) {
+ builder.append(", ");
+ }
+ }
+ builder.append("]");
+ }
+
+ builder.append("; bounds: " + mBounds);
+
+ builder.append("; packageName: ").append(mPackageName);
+ builder.append("; className: ").append(mClassName);
+ builder.append("; text: ").append(mText);
+ builder.append("; contentDescription: ").append(mContentDescription);
+
+ builder.append("; checkable: ").append(isCheckable());
+ builder.append("; checked: ").append(isChecked());
+ builder.append("; focusable: ").append(isFocusable());
+ builder.append("; focused: ").append(isFocused());
+ builder.append("; selected: ").append(isSelected());
+ builder.append("; clickable: ").append(isClickable());
+ builder.append("; longClickable: ").append(isLongClickable());
+ builder.append("; enabled: ").append(isEnabled());
+ builder.append("; password: ").append(isPassword());
+
+ builder.append("; [");
+
+ for (int actionBits = mActions; actionBits != 0;) {
+ final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
+ actionBits &= ~action;
+ builder.append(getActionSymbolicName(action));
+ if (actionBits != 0) {
+ builder.append(", ");
+ }
+ }
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
+ new Parcelable.Creator<AccessibilityNodeInfo>() {
+ public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ public AccessibilityNodeInfo[] newArray(int size) {
+ return new AccessibilityNodeInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
new file mode 100644
index 0000000..4bf03a7
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a record in an accessibility event. This class encapsulates
+ * the information for a {@link android.view.View}. Note that not all properties
+ * are applicable to all view types. For detailed information please refer to
+ * {@link AccessibilityEvent}.
+ *
+ * @see AccessibilityEvent
+ */
+public class AccessibilityRecord {
+
+ private static final int INVALID_POSITION = -1;
+
+ private static final int PROPERTY_CHECKED = 0x00000001;
+ private static final int PROPERTY_ENABLED = 0x00000002;
+ private static final int PROPERTY_PASSWORD = 0x00000004;
+ private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+
+ // Housekeeping
+ private static final int MAX_POOL_SIZE = 10;
+ private static final Object sPoolLock = new Object();
+ private static AccessibilityRecord sPool;
+ private static int sPoolSize;
+ private AccessibilityRecord mNext;
+ private boolean mIsInPool;
+ private boolean mSealed;
+
+ protected int mBooleanProperties;
+ protected int mCurrentItemIndex;
+ protected int mItemCount;
+ protected int mFromIndex;
+ protected int mAddedCount;
+ protected int mRemovedCount;
+
+ protected CharSequence mClassName;
+ protected CharSequence mContentDescription;
+ protected CharSequence mBeforeText;
+ protected Parcelable mParcelableData;
+
+ protected final List<CharSequence> mText = new ArrayList<CharSequence>();
+
+ /*
+ * Hide constructor.
+ */
+ protected AccessibilityRecord() {
+
+ }
+
+ /**
+ * Initialize this record from another one.
+ *
+ * @param record The to initialize from.
+ */
+ void init(AccessibilityRecord record) {
+ mSealed = record.isSealed();
+ mBooleanProperties = record.mBooleanProperties;
+ mCurrentItemIndex = record.mCurrentItemIndex;
+ mItemCount = record.mItemCount;
+ mFromIndex = record.mFromIndex;
+ mAddedCount = record.mAddedCount;
+ mRemovedCount = record.mRemovedCount;
+ mClassName = record.mClassName;
+ mContentDescription = record.mContentDescription;
+ mBeforeText = record.mBeforeText;
+ mParcelableData = record.mParcelableData;
+ mText.addAll(record.mText);
+ }
+
+ /**
+ * Gets if the source is checked.
+ *
+ * @return True if the view is checked, false otherwise.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets if the source is checked.
+ *
+ * @param isChecked True if the view is checked, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setChecked(boolean isChecked) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_CHECKED, isChecked);
+ }
+
+ /**
+ * Gets if the source is enabled.
+ *
+ * @return True if the view is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets if the source is enabled.
+ *
+ * @param isEnabled True if the view is enabled, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEnabled(boolean isEnabled) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_ENABLED, isEnabled);
+ }
+
+ /**
+ * Gets if the source is a password field.
+ *
+ * @return True if the view is a password field, false otherwise.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets if the source is a password field.
+ *
+ * @param isPassword True if the view is a password field, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPassword(boolean isPassword) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_PASSWORD, isPassword);
+ }
+
+ /**
+ * Gets if the source is taking the entire screen.
+ *
+ * @return True if the source is full screen, false otherwise.
+ */
+ public boolean isFullScreen() {
+ return getBooleanProperty(PROPERTY_FULL_SCREEN);
+ }
+
+ /**
+ * Sets if the source is taking the entire screen.
+ *
+ * @param isFullScreen True if the source is full screen, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFullScreen(boolean isFullScreen) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+ }
+
+ /**
+ * Gets the number of items that can be visited.
+ *
+ * @return The number of items.
+ */
+ public int getItemCount() {
+ return mItemCount;
+ }
+
+ /**
+ * Sets the number of items that can be visited.
+ *
+ * @param itemCount The number of items.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setItemCount(int itemCount) {
+ enforceNotSealed();
+ mItemCount = itemCount;
+ }
+
+ /**
+ * Gets the index of the source in the list of items the can be visited.
+ *
+ * @return The current item index.
+ */
+ public int getCurrentItemIndex() {
+ return mCurrentItemIndex;
+ }
+
+ /**
+ * Sets the index of the source in the list of items that can be visited.
+ *
+ * @param currentItemIndex The current item index.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setCurrentItemIndex(int currentItemIndex) {
+ enforceNotSealed();
+ mCurrentItemIndex = currentItemIndex;
+ }
+
+ /**
+ * Gets the index of the first character of the changed sequence.
+ *
+ * @return The index of the first character.
+ */
+ public int getFromIndex() {
+ return mFromIndex;
+ }
+
+ /**
+ * Sets the index of the first character of the changed sequence.
+ *
+ * @param fromIndex The index of the first character.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFromIndex(int fromIndex) {
+ enforceNotSealed();
+ mFromIndex = fromIndex;
+ }
+
+ /**
+ * Gets the number of added characters.
+ *
+ * @return The number of added characters.
+ */
+ public int getAddedCount() {
+ return mAddedCount;
+ }
+
+ /**
+ * Sets the number of added characters.
+ *
+ * @param addedCount The number of added characters.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAddedCount(int addedCount) {
+ enforceNotSealed();
+ mAddedCount = addedCount;
+ }
+
+ /**
+ * Gets the number of removed characters.
+ *
+ * @return The number of removed characters.
+ */
+ public int getRemovedCount() {
+ return mRemovedCount;
+ }
+
+ /**
+ * Sets the number of removed characters.
+ *
+ * @param removedCount The number of removed characters.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setRemovedCount(int removedCount) {
+ enforceNotSealed();
+ mRemovedCount = removedCount;
+ }
+
+ /**
+ * Gets the class name of the source.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class name of the source.
+ *
+ * @param className The lass name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClassName(CharSequence className) {
+ enforceNotSealed();
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of the event. The index in the list represents the priority
+ * of the text. Specifically, the lower the index the higher the priority.
+ *
+ * @return The text.
+ */
+ public List<CharSequence> getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @return The text before the change.
+ */
+ public CharSequence getBeforeText() {
+ return mBeforeText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @param beforeText The text before the change.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBeforeText(CharSequence beforeText) {
+ enforceNotSealed();
+ mBeforeText = beforeText;
+ }
+
+ /**
+ * Gets the description of the source.
+ *
+ * @return The description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the description of the source.
+ *
+ * @param contentDescription The description.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ enforceNotSealed();
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * Gets the {@link Parcelable} data.
+ *
+ * @return The parcelable data.
+ */
+ public Parcelable getParcelableData() {
+ return mParcelableData;
+ }
+
+ /**
+ * Sets the {@link Parcelable} data of the event.
+ *
+ * @param parcelableData The parcelable data.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setParcelableData(Parcelable parcelableData) {
+ enforceNotSealed();
+ mParcelableData = parcelableData;
+ }
+
+ /**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ public void setSealed(boolean sealed) {
+ mSealed = sealed;
+ }
+
+ /**
+ * Gets if this instance is sealed.
+ *
+ * @return Whether is sealed.
+ *
+ * @hide
+ */
+ public boolean isSealed() {
+ return mSealed;
+ }
+
+ /**
+ * Enforces that this instance is sealed.
+ *
+ * @throws IllegalStateException If this instance is not sealed.
+ *
+ * @hide
+ */
+ protected void enforceSealed() {
+ if (!isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a not sealed instance.");
+ }
+ }
+
+ /**
+ * Enforces that this instance is not sealed.
+ *
+ * @throws IllegalStateException If this instance is sealed.
+ *
+ * @hide
+ */
+ protected void enforceNotSealed() {
+ if (isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on an sealed instance.");
+ }
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) == property;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated. The instance is initialized with data from the
+ * given record.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityRecord obtain(AccessibilityRecord record) {
+ AccessibilityRecord clone = AccessibilityRecord.obtain();
+ clone.init(record);
+ return clone;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityRecord obtain() {
+ synchronized (sPoolLock) {
+ if (sPool != null) {
+ AccessibilityRecord record = sPool;
+ sPool = sPool.mNext;
+ sPoolSize--;
+ record.mNext = null;
+ record.mIsInPool = false;
+ return record;
+ }
+ return new AccessibilityRecord();
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <b>Note: You must not touch the object after calling this function.</b>
+ *
+ * @throws IllegalStateException If the record is already recycled.
+ */
+ public void recycle() {
+ if (mIsInPool) {
+ throw new IllegalStateException("Record already recycled!");
+ }
+ clear();
+ synchronized (sPoolLock) {
+ if (sPoolSize <= MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ mIsInPool = true;
+ sPoolSize++;
+ }
+ }
+ }
+
+ /**
+ * Clears the state of this instance.
+ *
+ * @hide
+ */
+ protected void clear() {
+ mSealed = false;
+ mBooleanProperties = 0;
+ mCurrentItemIndex = INVALID_POSITION;
+ mItemCount = 0;
+ mFromIndex = 0;
+ mAddedCount = 0;
+ mRemovedCount = 0;
+ mClassName = null;
+ mContentDescription = null;
+ mBeforeText = null;
+ mParcelableData = null;
+ mText.clear();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(" [ ClassName: " + mClassName);
+ builder.append("; Text: " + mText);
+ builder.append("; ContentDescription: " + mContentDescription);
+ builder.append("; ItemCount: " + mItemCount);
+ builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
+ builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
+ builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
+ builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
+ builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
+ builder.append("; BeforeText: " + mBeforeText);
+ builder.append("; FromIndex: " + mFromIndex);
+ builder.append("; AddedCount: " + mAddedCount);
+ builder.append("; RemovedCount: " + mRemovedCount);
+ builder.append("; ParcelableData: " + mParcelableData);
+ builder.append(" ]");
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
new file mode 100644
index 0000000..77dcd07
--- /dev/null
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+/**
+ * Interface for interaction between the AccessibilityManagerService
+ * and the ViewRoot in a given window.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityInteractionConnection {
+
+ void findAccessibilityNodeInfoByAccessibilityId(int accessibilityViewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+
+ void findAccessibilityNodeInfoByViewId(int id, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+
+ void findAccessibilityNodeInfosByViewText(String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+
+ void performAccessibilityAction(int accessibilityId, int action, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback);
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
new file mode 100644
index 0000000..9c5e8dc
--- /dev/null
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.List;
+
+/**
+ * Callback for specifying the result for an asynchronous request made
+ * via calling a method on IAccessibilityInteractionConnectionCallback.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityInteractionConnectionCallback {
+
+ void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
+
+ void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
+ int interactionId);
+
+ void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 7633569..b14f02a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -17,9 +17,14 @@
package android.view.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.accessibilityservice.IEventListener;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityManagerClient;
-import android.content.pm.ServiceInfo;
+import android.view.IWindow;
/**
* Interface implemented by the AccessibilityManagerService called by
@@ -33,7 +38,16 @@ interface IAccessibilityManager {
boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent);
- List<ServiceInfo> getAccessibilityServiceList();
+ List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
+
+ List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType);
void interrupt();
+
+ int addAccessibilityInteractionConnection(IWindow windowToken,
+ in IAccessibilityInteractionConnection connection);
+
+ void removeAccessibilityInteractionConnection(IWindow windowToken);
+
+ IAccessibilityServiceConnection registerEventListener(IEventListener client);
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index e644045..abe3c2c 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -34,7 +34,7 @@ import android.util.LogPrinter;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
class ComposingText implements NoCopySpan {
}
@@ -49,8 +49,9 @@ public class BaseInputConnection implements InputConnection {
private static final boolean DEBUG = false;
private static final String TAG = "BaseInputConnection";
static final Object COMPOSING = new ComposingText();
-
- final InputMethodManager mIMM;
+
+ /** @hide */
+ protected final InputMethodManager mIMM;
final View mTargetView;
final boolean mDummyMode;
@@ -501,7 +502,7 @@ public class BaseInputConnection implements InputConnection {
}
}
if (h != null) {
- h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ h.sendMessage(h.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
event));
}
}
@@ -644,7 +645,7 @@ public class BaseInputConnection implements InputConnection {
lp.println("Composing text:");
TextUtils.dumpSpans(text, lp, " ");
}
-
+
// Position the cursor appropriately, so that after replacing the
// desired range of text it will be located in the correct spot.
// This allows us to deal with filters performing edits on the text
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 4d9d51e..690ea85 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -58,8 +58,7 @@ public class InputConnectionWrapper implements InputConnection {
return mTarget.getCursorCapsMode(reqModes);
}
- public ExtractedText getExtractedText(ExtractedTextRequest request,
- int flags) {
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
return mTarget.getExtractedText(request, flags);
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 32eec9f..4ec4ff9 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -23,9 +23,9 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -38,6 +38,8 @@ import android.util.Xml;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
/**
* This class is used to specify meta information of an input method.
@@ -77,13 +79,28 @@ public final class InputMethodInfo implements Parcelable {
/**
* Constructor.
- *
+ *
* @param context The Context in which we are parsing the input method.
* @param service The ResolveInfo returned from the package manager about
* this input method's component.
*/
public InputMethodInfo(Context context, ResolveInfo service)
throws XmlPullParserException, IOException {
+ this(context, service, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context The Context in which we are parsing the input method.
+ * @param service The ResolveInfo returned from the package manager about
+ * this input method's component.
+ * @param additionalSubtypes additional subtypes being added to this InputMethodInfo
+ * @hide
+ */
+ public InputMethodInfo(Context context, ResolveInfo service,
+ Map<String, List<InputMethodSubtype>> additionalSubtypesMap)
+ throws XmlPullParserException, IOException {
mService = service;
ServiceInfo si = service.serviceInfo;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -145,7 +162,9 @@ public final class InputMethodInfo implements Parcelable {
a.getString(com.android.internal.R.styleable
.InputMethod_Subtype_imeSubtypeMode),
a.getString(com.android.internal.R.styleable
- .InputMethod_Subtype_imeSubtypeExtraValue));
+ .InputMethod_Subtype_imeSubtypeExtraValue),
+ a.getBoolean(com.android.internal.R.styleable
+ .InputMethod_Subtype_isAuxiliary, false));
mSubtypes.add(subtype);
}
}
@@ -155,6 +174,17 @@ public final class InputMethodInfo implements Parcelable {
} finally {
if (parser != null) parser.close();
}
+
+ if (additionalSubtypesMap != null && additionalSubtypesMap.containsKey(mId)) {
+ final List<InputMethodSubtype> additionalSubtypes = additionalSubtypesMap.get(mId);
+ final int N = additionalSubtypes.size();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype subtype = additionalSubtypes.get(i);
+ if (!mSubtypes.contains(subtype)) {
+ mSubtypes.add(subtype);
+ }
+ }
+ }
mSettingsActivityName = settingsActivityComponent;
mIsDefaultResId = isDefaultResId;
}
@@ -322,7 +352,12 @@ public final class InputMethodInfo implements Parcelable {
InputMethodInfo obj = (InputMethodInfo) o;
return mId.equals(obj.mId);
}
-
+
+ @Override
+ public int hashCode() {
+ return mId.hashCode();
+ }
+
/**
* Used to package this object into a {@link Parcel}.
*
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 83d2a79..47f5e4c 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -35,13 +35,14 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -505,6 +506,13 @@ public final class InputMethodManager {
}
}
+ /**
+ * Returns a list of enabled input method subtypes for the specified input method info.
+ * @param imi An input method info whose subtypes list will be returned.
+ * @param allowsImplicitlySelectedSubtypes A boolean flag to allow to return the implicitly
+ * selected subtypes. If an input method info doesn't have enabled subtypes, the framework
+ * will implicitly enable subtypes according to the current system language.
+ */
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes) {
try {
@@ -543,7 +551,25 @@ public final class InputMethodManager {
public void setFullscreenMode(boolean fullScreen) {
mFullscreenMode = fullScreen;
}
-
+
+ /** @hide */
+ public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+ try {
+ mService.registerSuggestionSpansForNotification(spans);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** @hide */
+ public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+ try {
+ mService.notifySuggestionPicked(span, originalString, index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Allows you to discover whether the attached input method is running
* in fullscreen mode. Return true if it is fullscreen, entirely covering
@@ -629,7 +655,7 @@ public final class InputMethodManager {
if (vh != null) {
// This will result in a call to reportFinishInputConnection()
// below.
- vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION,
+ vh.sendMessage(vh.obtainMessage(ViewAncestor.FINISH_INPUT_CONNECTION,
mServedInputConnection));
}
}
@@ -1086,9 +1112,9 @@ public final class InputMethodManager {
void scheduleCheckFocusLocked(View view) {
Handler vh = view.getHandler();
- if (vh != null && !vh.hasMessages(ViewRoot.CHECK_FOCUS)) {
+ if (vh != null && !vh.hasMessages(ViewAncestor.CHECK_FOCUS)) {
// This will result in a call to checkFocus() below.
- vh.sendMessage(vh.obtainMessage(ViewRoot.CHECK_FOCUS));
+ vh.sendMessage(vh.obtainMessage(ViewAncestor.CHECK_FOCUS));
}
}
@@ -1143,7 +1169,7 @@ public final class InputMethodManager {
}
/**
- * Called by ViewRoot when its window gets input focus.
+ * Called by ViewAncestor when its window gets input focus.
* @hide
*/
public void onWindowFocus(View rootView, View focusedView, int softInputMode,
@@ -1429,16 +1455,26 @@ public final class InputMethodManager {
}
}
- public void showInputMethodAndSubtypeEnabler(String topId) {
+ /**
+ * Show the settings for enabling subtypes of the specified input method.
+ * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
+ * subtypes of all input methods will be shown.
+ */
+ public void showInputMethodAndSubtypeEnabler(String imiId) {
synchronized (mH) {
try {
- mService.showInputMethodAndSubtypeEnablerFromClient(mClient, topId);
+ mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
}
+ /**
+ * Returns the current input method subtype. This subtype is one of the subtypes in
+ * the current input method. This method returns null when the current input method doesn't
+ * have any input method subtype.
+ */
public InputMethodSubtype getCurrentInputMethodSubtype() {
synchronized (mH) {
try {
@@ -1450,6 +1486,12 @@ public final class InputMethodManager {
}
}
+ /**
+ * Switch to a new input method subtype of the current input method.
+ * @param subtype A new input method subtype to switch.
+ * @return true if the current subtype was successfully switched. When the specified subtype is
+ * null, this method returns false.
+ */
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
synchronized (mH) {
try {
@@ -1461,6 +1503,9 @@ public final class InputMethodManager {
}
}
+ /**
+ * Returns a map of all shortcut input method info and their subtypes.
+ */
public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
synchronized (mH) {
HashMap<InputMethodInfo, List<InputMethodSubtype>> ret =
@@ -1493,6 +1538,15 @@ public final class InputMethodManager {
}
}
+ /**
+ * Force switch to the last used input method and subtype. If the last input method didn't have
+ * any subtypes, the framework will simply switch to the last input method with no subtype
+ * specified.
+ * @param imeToken Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ * @return true if the current input method and subtype was successfully switched to the last
+ * used input method and subtype.
+ */
public boolean switchToLastInputMethod(IBinder imeToken) {
synchronized (mH) {
try {
@@ -1504,6 +1558,35 @@ public final class InputMethodManager {
}
}
+ /**
+ * Set additional input method subtypes.
+ * @param imeToken Supplies the identifying token given to an input method.
+ * @param subtypes subtypes will be added as additional subtypes of the current input method.
+ * @return true if the additional input method subtypes are successfully added.
+ */
+ public boolean setAdditionalInputMethodSubtypes(
+ IBinder imeToken, InputMethodSubtype[] subtypes) {
+ synchronized (mH) {
+ try {
+ return mService.setAdditionalInputMethodSubtypes(imeToken, subtypes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ return false;
+ }
+ }
+ }
+
+ public InputMethodSubtype getLastInputMethodSubtype() {
+ synchronized (mH) {
+ try {
+ return mService.getLastInputMethodSubtype();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ return null;
+ }
+ }
+ }
+
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 807f6ce..9d84c3e 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -17,8 +17,10 @@
package android.view.inputmethod;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Slog;
import java.util.ArrayList;
@@ -26,6 +28,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
/**
* This class is used to specify meta information of a subtype contained in an input method.
@@ -38,12 +41,13 @@ public final class InputMethodSubtype implements Parcelable {
private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
- private final int mSubtypeNameResId;
+ private final boolean mIsAuxiliary;
+ private final int mSubtypeHashCode;
private final int mSubtypeIconResId;
+ private final int mSubtypeNameResId;
private final String mSubtypeLocale;
private final String mSubtypeMode;
private final String mSubtypeExtraValue;
- private final int mSubtypeHashCode;
private HashMap<String, String> mExtraValueHashMapCache;
/**
@@ -51,16 +55,33 @@ public final class InputMethodSubtype implements Parcelable {
* @param nameId The name of the subtype
* @param iconId The icon of the subtype
* @param locale The locale supported by the subtype
- * @param modeId The mode supported by the subtype
+ * @param mode The mode supported by the subtype
+ * @param extraValue The extra value of the subtype
+ */
+ public InputMethodSubtype(
+ int nameId, int iconId, String locale, String mode, String extraValue) {
+ this(nameId, iconId, locale, mode, extraValue, false);
+ }
+
+ /**
+ * Constructor
+ * @param nameId The name of the subtype
+ * @param iconId The icon of the subtype
+ * @param locale The locale supported by the subtype
+ * @param mode The mode supported by the subtype
* @param extraValue The extra value of the subtype
+ * @param isAuxiliary true when this subtype is one shot subtype.
*/
- InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue) {
+ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
+ boolean isAuxiliary) {
mSubtypeNameResId = nameId;
mSubtypeIconResId = iconId;
mSubtypeLocale = locale != null ? locale : "";
mSubtypeMode = mode != null ? mode : "";
mSubtypeExtraValue = extraValue != null ? extraValue : "";
- mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue);
+ mIsAuxiliary = isAuxiliary;
+ mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
+ mIsAuxiliary);
}
InputMethodSubtype(Parcel source) {
@@ -73,7 +94,9 @@ public final class InputMethodSubtype implements Parcelable {
mSubtypeMode = s != null ? s : "";
s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
- mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue);
+ mIsAuxiliary = (source.readInt() == 1);
+ mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
+ mIsAuxiliary);
}
/**
@@ -111,6 +134,41 @@ public final class InputMethodSubtype implements Parcelable {
return mSubtypeExtraValue;
}
+ /**
+ * @return true if this subtype is one shot subtype. One shot subtype will not be shown in the
+ * ime switch list when this subtype is implicitly enabled. The framework will never
+ * switch the current ime to this subtype by switchToLastInputMethod in InputMethodManager.
+ */
+ public boolean isAuxiliary() {
+ return mIsAuxiliary;
+ }
+
+ /**
+ * @param context Context will be used for getting Locale and PackageManager.
+ * @param packageName The package name of the IME
+ * @param appInfo The application info of the IME
+ * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
+ * can have only one %s in it. If there is, the %s part will be replaced with the locale's
+ * display name by the formatter. If there is not, this method simply returns the string
+ * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
+ * framework to generate an appropriate display name.
+ */
+ public CharSequence getDisplayName(
+ Context context, String packageName, ApplicationInfo appInfo) {
+ final Locale locale = constructLocaleFromString(mSubtypeLocale);
+ final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
+ if (mSubtypeNameResId == 0) {
+ return localeStr;
+ }
+ final String subtypeName = context.getPackageManager().getText(
+ packageName, mSubtypeNameResId, appInfo).toString();
+ if (!TextUtils.isEmpty(subtypeName)) {
+ return String.format(subtypeName, localeStr);
+ } else {
+ return localeStr;
+ }
+ }
+
private HashMap<String, String> getExtraValueHashMap() {
if (mExtraValueHashMapCache == null) {
mExtraValueHashMapCache = new HashMap<String, String>();
@@ -165,36 +223,59 @@ public final class InputMethodSubtype implements Parcelable {
&& (subtype.getMode().equals(getMode()))
&& (subtype.getIconResId() == getIconResId())
&& (subtype.getLocale().equals(getLocale()))
- && (subtype.getExtraValue().equals(getExtraValue()));
+ && (subtype.getExtraValue().equals(getExtraValue()))
+ && (subtype.isAuxiliary() == isAuxiliary());
}
return false;
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
dest.writeInt(mSubtypeIconResId);
dest.writeString(mSubtypeLocale);
dest.writeString(mSubtypeMode);
dest.writeString(mSubtypeExtraValue);
+ dest.writeInt(mIsAuxiliary ? 1 : 0);
}
public static final Parcelable.Creator<InputMethodSubtype> CREATOR
= new Parcelable.Creator<InputMethodSubtype>() {
+ @Override
public InputMethodSubtype createFromParcel(Parcel source) {
return new InputMethodSubtype(source);
}
+ @Override
public InputMethodSubtype[] newArray(int size) {
return new InputMethodSubtype[size];
}
};
- private static int hashCodeInternal(String locale, String mode, String extraValue) {
- return Arrays.hashCode(new Object[] {locale, mode, extraValue});
+ private static Locale constructLocaleFromString(String localeStr) {
+ if (TextUtils.isEmpty(localeStr))
+ return null;
+ String[] localeParams = localeStr.split("_", 3);
+ // The length of localeStr is guaranteed to always return a 1 <= value <= 3
+ // because localeStr is not empty.
+ if (localeParams.length == 1) {
+ return new Locale(localeParams[0]);
+ } else if (localeParams.length == 2) {
+ return new Locale(localeParams[0], localeParams[1]);
+ } else if (localeParams.length == 3) {
+ return new Locale(localeParams[0], localeParams[1], localeParams[2]);
+ }
+ return null;
+ }
+
+ private static int hashCodeInternal(String locale, String mode, String extraValue,
+ boolean isAuxiliary) {
+ return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary});
}
/**
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index c7a7374..2f4774f 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -35,7 +35,7 @@ import android.os.Message;
import android.util.Log;
import android.util.TypedValue;
import android.view.Surface;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
import android.view.WindowManager;
import junit.framework.Assert;
@@ -44,6 +44,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.URLEncoder;
+import java.nio.charset.Charsets;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
@@ -222,7 +225,7 @@ class BrowserFrame extends Handler {
sConfigCallback = new ConfigCallback(
(WindowManager) appContext.getSystemService(
Context.WINDOW_SERVICE));
- ViewRoot.addConfigCallback(sConfigCallback);
+ ViewAncestor.addConfigCallback(sConfigCallback);
}
sConfigCallback.addHandler(this);
@@ -1043,13 +1046,16 @@ class BrowserFrame extends Handler {
// These ids need to be in sync with enum rawResId in PlatformBridge.h
private static final int NODOMAIN = 1;
private static final int LOADERROR = 2;
- private static final int DRAWABLEDIR = 3;
+ /* package */ static final int DRAWABLEDIR = 3;
private static final int FILE_UPLOAD_LABEL = 4;
private static final int RESET_LABEL = 5;
private static final int SUBMIT_LABEL = 6;
private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7;
- String getRawResFilename(int id) {
+ private String getRawResFilename(int id) {
+ return getRawResFilename(id, mContext);
+ }
+ /* package */ static String getRawResFilename(int id, Context context) {
int resid;
switch (id) {
case NODOMAIN:
@@ -1066,19 +1072,19 @@ class BrowserFrame extends Handler {
break;
case FILE_UPLOAD_LABEL:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.upload_file);
case RESET_LABEL:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.reset);
case SUBMIT_LABEL:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.submit);
case FILE_UPLOAD_NO_FILE_CHOSEN:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.no_file_chosen);
default:
@@ -1086,7 +1092,7 @@ class BrowserFrame extends Handler {
return "";
}
TypedValue value = new TypedValue();
- mContext.getResources().getValue(resid, value, true);
+ context.getResources().getValue(resid, value, true);
if (id == DRAWABLEDIR) {
String path = value.string.toString();
int index = path.lastIndexOf('/');
@@ -1138,7 +1144,7 @@ class BrowserFrame extends Handler {
}
/**
- * Called by JNI when the native HTTP(S) stack gets an invalid cert chain.
+ * Called by JNI when the native HTTPS stack gets an invalid cert chain.
*
* We delegate the request to CallbackProxy, and route its response to
* {@link #nativeSslCertErrorProceed(int)} or
@@ -1179,6 +1185,32 @@ class BrowserFrame extends Handler {
}
/**
+ * Called by JNI when the native HTTPS stack gets a client
+ * certificate request.
+ *
+ * We delegate the request to CallbackProxy, and route its response to
+ * {@link #nativeSslClientCert(int, X509Certificate)}.
+ */
+ private void requestClientCert(int handle, byte[] host_and_port_bytes) {
+ String host_and_port = new String(host_and_port_bytes, Charsets.UTF_8);
+ SslClientCertLookupTable table = SslClientCertLookupTable.getInstance();
+ if (table.IsAllowed(host_and_port)) {
+ // previously allowed
+ nativeSslClientCert(handle,
+ table.PrivateKey(host_and_port),
+ table.CertificateChain(host_and_port));
+ } else if (table.IsDenied(host_and_port)) {
+ // previously denied
+ nativeSslClientCert(handle, null, null);
+ } else {
+ // previously ignored or new
+ mCallbackProxy.onReceivedClientCertRequest(
+ new ClientCertRequestHandler(this, handle, host_and_port, table),
+ host_and_port);
+ }
+ }
+
+ /**
* Called by JNI when the native HTTP stack needs to download a file.
*
* We delegate the request to CallbackProxy, which owns the current app's
@@ -1363,4 +1395,8 @@ class BrowserFrame extends Handler {
private native void nativeSslCertErrorProceed(int handle);
private native void nativeSslCertErrorCancel(int handle, int cert_error);
+
+ native void nativeSslClientCert(int handle,
+ byte[] pkcs8EncodedPrivateKey,
+ byte[][] asn1DerEncodedCertificateChain);
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 23fd12d..f7d55f6 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -118,6 +118,7 @@ class CallbackProxy extends Handler {
private static final int SET_INSTALLABLE_WEBAPP = 138;
private static final int NOTIFY_SEARCHBOX_LISTENERS = 139;
private static final int AUTO_LOGIN = 140;
+ private static final int CLIENT_CERT_REQUEST = 141;
// Message triggered by the client to resume execution
private static final int NOTIFY = 200;
@@ -273,7 +274,7 @@ class CallbackProxy extends Handler {
mWebViewClient.onPageFinished(mWebView, finishedUrl);
}
break;
-
+
case RECEIVED_ICON:
if (mWebChromeClient != null) {
mWebChromeClient.onReceivedIcon(mWebView, (Bitmap) msg.obj);
@@ -340,7 +341,7 @@ class CallbackProxy extends Handler {
case SSL_ERROR:
if (mWebViewClient != null) {
- HashMap<String, Object> map =
+ HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
mWebViewClient.onReceivedSslError(mWebView,
(SslErrorHandler) map.get("handler"),
@@ -348,6 +349,16 @@ class CallbackProxy extends Handler {
}
break;
+ case CLIENT_CERT_REQUEST:
+ if (mWebViewClient != null) {
+ HashMap<String, Object> map =
+ (HashMap<String, Object>) msg.obj;
+ mWebViewClient.onReceivedClientCertRequest(mWebView,
+ (ClientCertRequestHandler) map.get("handler"),
+ (String) map.get("host_and_port"));
+ }
+ break;
+
case PROGRESS:
// Synchronize to ensure mLatestProgress is not modified after
// setProgress is called and before mProgressUpdatePending is
@@ -543,14 +554,14 @@ class CallbackProxy extends Handler {
new AlertDialog.Builder(mContext)
.setTitle(getJsDialogTitle(url))
.setMessage(message)
- .setPositiveButton(R.string.ok,
+ .setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.confirm();
}})
- .setNegativeButton(R.string.cancel,
+ .setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
@@ -904,7 +915,7 @@ class CallbackProxy extends Handler {
if (PERF_PROBE) {
// un-comment this if PERF_PROBE is true
// Looper.myQueue().setWaitCallback(null);
- Log.d("WebCore", "WebCore thread used " +
+ Log.d("WebCore", "WebCore thread used " +
(SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
+ " ms and idled " + mWebCoreIdleTime + " ms");
Network.getInstance(mContext).stopTiming();
@@ -934,7 +945,7 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
- public void onFormResubmission(Message dontResend,
+ public void onFormResubmission(Message dontResend,
Message resend) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
@@ -998,7 +1009,6 @@ class CallbackProxy extends Handler {
return;
}
Message msg = obtainMessage(SSL_ERROR);
- //, handler);
HashMap<String, Object> map = new HashMap();
map.put("handler", handler);
map.put("error", error);
@@ -1006,6 +1016,23 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
/**
+ * @hide
+ */
+ public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
+ // Do an unsynchronized quick check to avoid posting if no callback has
+ // been set.
+ if (mWebViewClient == null) {
+ handler.cancel();
+ return;
+ }
+ Message msg = obtainMessage(CLIENT_CERT_REQUEST);
+ HashMap<String, Object> map = new HashMap();
+ map.put("handler", handler);
+ map.put("host_and_port", host_and_port);
+ msg.obj = map;
+ sendMessage(msg);
+ }
+ /**
* @hide - hide this because it contains a parameter of type SslCertificate,
* which is located in a hidden package.
*/
diff --git a/core/java/android/webkit/ClientCertRequestHandler.java b/core/java/android/webkit/ClientCertRequestHandler.java
new file mode 100644
index 0000000..3a71e7e
--- /dev/null
+++ b/core/java/android/webkit/ClientCertRequestHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import org.apache.harmony.xnet.provider.jsse.NativeCrypto;
+
+/**
+ * ClientCertRequestHandler: class responsible for handling client
+ * certificate requests. This class is passed as a parameter to
+ * BrowserCallback.displayClientCertRequestDialog and is meant to
+ * receive the user's response.
+ *
+ * @hide
+ */
+public final class ClientCertRequestHandler {
+
+ private final BrowserFrame mBrowserFrame;
+ private final int mHandle;
+ private final String mHostAndPort;
+ private final SslClientCertLookupTable mTable;
+ ClientCertRequestHandler(BrowserFrame browserFrame,
+ int handle,
+ String host_and_port,
+ SslClientCertLookupTable table) {
+ mBrowserFrame = browserFrame;
+ mHandle = handle;
+ mHostAndPort = host_and_port;
+ mTable = table;
+ }
+
+ /**
+ * Proceed with the specified private key and client certificate chain.
+ */
+ public void proceed(PrivateKey privateKey, X509Certificate[] chain) {
+ byte[] privateKeyBytes = privateKey.getEncoded();
+ byte[][] chainBytes;
+ try {
+ chainBytes = NativeCrypto.encodeCertificates(chain);
+ } catch (CertificateEncodingException e) {
+ mBrowserFrame.nativeSslClientCert(mHandle, null, null);
+ return;
+ }
+ mTable.Allow(mHostAndPort, privateKeyBytes, chainBytes);
+ mBrowserFrame.nativeSslClientCert(mHandle, privateKeyBytes, chainBytes);
+ }
+
+ /**
+ * Igore the request for now, the user may be prompted again.
+ */
+ public void ignore() {
+ mBrowserFrame.nativeSslClientCert(mHandle, null, null);
+ }
+
+ /**
+ * Cancel this request, remember the users negative choice.
+ */
+ public void cancel() {
+ mTable.Deny(mHostAndPort);
+ mBrowserFrame.nativeSslClientCert(mHandle, null, null);
+ }
+}
diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java
index 235dc5be..e8d9069 100644
--- a/core/java/android/webkit/DataLoader.java
+++ b/core/java/android/webkit/DataLoader.java
@@ -22,7 +22,7 @@ import com.android.internal.R;
import java.io.ByteArrayInputStream;
-import org.apache.harmony.luni.util.Base64;
+import libcore.io.Base64;
/**
* This class is a concrete implementation of StreamLoader that uses the
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 0918683..57cda97 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -199,6 +199,9 @@ public class HTML5VideoFullScreen extends HTML5VideoView
mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
}
+ public boolean fullScreenExited() {
+ return (mLayout == null);
+ }
private final WebChromeClient.CustomViewCallback mCallback =
new WebChromeClient.CustomViewCallback() {
@@ -208,7 +211,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView
// view. This happens in the WebChromeClient before this method
// is invoked.
pauseAndDispatch(mProxy);
-
+ mProxy.dispatchOnStopFullScreen();
mLayout.removeView(getSurfaceView());
if (mProgressView != null) {
@@ -253,7 +256,8 @@ public class HTML5VideoFullScreen extends HTML5VideoView
client.onShowCustomView(mLayout, mCallback);
// Plugins like Flash will draw over the video so hide
// them while we're playing.
- mProxy.getWebView().getViewManager().hideAll();
+ if (webView.getViewManager() != null)
+ webView.getViewManager().hideAll();
mProgressView = client.getVideoLoadingProgressView();
if (mProgressView != null) {
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index 25921bc..ef1906c 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -12,10 +12,15 @@ import android.opengl.GLES20;
*/
public class HTML5VideoInline extends HTML5VideoView{
- // Due to the fact that SurfaceTexture consume a lot of memory, we make it
- // as static. m_textureNames is the texture bound with this SurfaceTexture.
+ // Due to the fact that the decoder consume a lot of memory, we make the
+ // surface texture as singleton. But the GL texture (m_textureNames)
+ // associated with the surface texture can be used for showing the screen
+ // shot when paused, so they are not singleton.
private static SurfaceTexture mSurfaceTexture = null;
- private static int[] mTextureNames;
+ private int[] mTextureNames;
+ // Every time when the VideoLayer Id change, we need to recreate the
+ // SurfaceTexture in order to delete the old video's decoder memory.
+ private static int mVideoLayerUsingSurfaceTexture = -1;
// Video control FUNCTIONS:
@Override
@@ -28,11 +33,12 @@ public class HTML5VideoInline extends HTML5VideoView{
HTML5VideoInline(int videoLayerId, int position,
boolean autoStart) {
init(videoLayerId, position, autoStart);
+ mTextureNames = null;
}
@Override
public void decideDisplayMode() {
- mPlayer.setTexture(getSurfaceTextureInstance());
+ mPlayer.setTexture(getSurfaceTexture(getVideoLayerId()));
}
// Normally called immediately after setVideoURI. But for full screen,
@@ -52,31 +58,38 @@ public class HTML5VideoInline extends HTML5VideoView{
// Inline Video specific FUNCTIONS:
@Override
- public SurfaceTexture getSurfaceTexture() {
+ public SurfaceTexture getSurfaceTexture(int videoLayerId) {
+ // Create the surface texture.
+ if (videoLayerId != mVideoLayerUsingSurfaceTexture
+ || mSurfaceTexture == null) {
+ if (mTextureNames == null) {
+ mTextureNames = new int[1];
+ GLES20.glGenTextures(1, mTextureNames, 0);
+ }
+ mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
+ }
+ mVideoLayerUsingSurfaceTexture = videoLayerId;
return mSurfaceTexture;
}
+ public boolean surfaceTextureDeleted() {
+ return (mSurfaceTexture == null);
+ }
+
@Override
public void deleteSurfaceTexture() {
mSurfaceTexture = null;
+ mVideoLayerUsingSurfaceTexture = -1;
return;
}
- // SurfaceTexture is a singleton here , too
- private SurfaceTexture getSurfaceTextureInstance() {
- // Create the surface texture.
- if (mSurfaceTexture == null)
- {
- mTextureNames = new int[1];
- GLES20.glGenTextures(1, mTextureNames, 0);
- mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
- }
- return mSurfaceTexture;
- }
-
@Override
public int getTextureName() {
- return mTextureNames[0];
+ if (mTextureNames != null) {
+ return mTextureNames[0];
+ } else {
+ return 0;
+ }
}
private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java
index c05498a..5983a44 100644
--- a/core/java/android/webkit/HTML5VideoView.java
+++ b/core/java/android/webkit/HTML5VideoView.java
@@ -287,7 +287,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
return false;
}
- public SurfaceTexture getSurfaceTexture() {
+ public SurfaceTexture getSurfaceTexture(int videoLayerId) {
return null;
}
@@ -315,4 +315,14 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
// Only used in HTML5VideoFullScreen
}
+ public boolean surfaceTextureDeleted() {
+ // Only meaningful for HTML5VideoInline
+ return false;
+ }
+
+ public boolean fullScreenExited() {
+ // Only meaningful for HTML5VideoFullScreen
+ return false;
+ }
+
}
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index d1b8cfc..d0237b5 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -65,6 +65,7 @@ class HTML5VideoViewProxy extends Handler
private static final int ENDED = 201;
private static final int POSTER_FETCHED = 202;
private static final int PAUSED = 203;
+ private static final int STOPFULLSCREEN = 204;
// Timer thread -> UI thread
private static final int TIMEUPDATE = 300;
@@ -105,12 +106,14 @@ class HTML5VideoViewProxy extends Handler
public static void setBaseLayer(int layer) {
// Don't do this for full screen mode.
if (mHTML5VideoView != null
- && !mHTML5VideoView.isFullScreenMode()) {
+ && !mHTML5VideoView.isFullScreenMode()
+ && !mHTML5VideoView.surfaceTextureDeleted()) {
mBaseLayer = layer;
- SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture();
- int textureName = mHTML5VideoView.getTextureName();
int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
+ SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(currentVideoLayerId);
+ int textureName = mHTML5VideoView.getTextureName();
+
if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
int playerState = mHTML5VideoView.getCurrentState();
if (mHTML5VideoView.getPlayerBuffering())
@@ -170,14 +173,12 @@ class HTML5VideoViewProxy extends Handler
boolean backFromFullScreenMode = false;
if (mHTML5VideoView != null) {
currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
- if (mHTML5VideoView instanceof HTML5VideoFullScreen) {
- backFromFullScreenMode = true;
- }
+ backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
}
if (backFromFullScreenMode
- || currentVideoLayerId != videoLayerId
- || mHTML5VideoView.getSurfaceTexture() == null) {
+ || currentVideoLayerId != videoLayerId
+ || mHTML5VideoView.surfaceTextureDeleted()) {
// Here, we handle the case when switching to a new video,
// either inside a WebView or across WebViews
// For switching videos within a WebView or across the WebView,
@@ -287,8 +288,13 @@ class HTML5VideoViewProxy extends Handler
}
public void dispatchOnPaused() {
- Message msg = Message.obtain(mWebCoreHandler, PAUSED);
- mWebCoreHandler.sendMessage(msg);
+ Message msg = Message.obtain(mWebCoreHandler, PAUSED);
+ mWebCoreHandler.sendMessage(msg);
+ }
+
+ public void dispatchOnStopFullScreen() {
+ Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
+ mWebCoreHandler.sendMessage(msg);
}
public void onTimeupdate() {
@@ -560,6 +566,9 @@ class HTML5VideoViewProxy extends Handler
case TIMEUPDATE:
nativeOnTimeupdate(msg.arg1, mNativePointer);
break;
+ case STOPFULLSCREEN:
+ nativeOnStopFullscreen(mNativePointer);
+ break;
}
}
};
@@ -686,6 +695,7 @@ class HTML5VideoViewProxy extends Handler
private native void nativeOnPaused(int nativePointer);
private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
private native void nativeOnTimeupdate(int position, int nativePointer);
+ private native void nativeOnStopFullscreen(int nativePointer);
private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
int baseLayer, int videoLayerId, int textureName,
int playerState);
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
index 62b415c..b5d4933 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -48,6 +48,12 @@ class JniUtil {
initialized = true;
}
+ protected static synchronized Context getContext() {
+ if (!initialized)
+ return null;
+ return sContext;
+ }
+
/**
* Called by JNI. Gets the application's database directory, excluding the trailing slash.
* @return String The application's database directory
diff --git a/core/java/android/webkit/L10nUtils.java b/core/java/android/webkit/L10nUtils.java
index f59d7d0..5b4fb1d 100644
--- a/core/java/android/webkit/L10nUtils.java
+++ b/core/java/android/webkit/L10nUtils.java
@@ -69,7 +69,8 @@ public class L10nUtils {
com.android.internal.R.string.autofill_card_number_re, // IDS_AUTOFILL_CARD_NUMBER_RE
com.android.internal.R.string.autofill_expiration_month_re, // IDS_AUTOFILL_EXPIRATION_MONTH_RE
com.android.internal.R.string.autofill_expiration_date_re, // IDS_AUTOFILL_EXPIRATION_DATE_RE
- com.android.internal.R.string.autofill_card_ignored_re // IDS_AUTOFILL_CARD_IGNORED_RE
+ com.android.internal.R.string.autofill_card_ignored_re, // IDS_AUTOFILL_CARD_IGNORED_RE
+ com.android.internal.R.string.autofill_fax_re // IDS_AUTOFILL_FAX_RE
};
private static Context mApplicationContext;
diff --git a/core/java/android/webkit/PluginFullScreenHolder.java b/core/java/android/webkit/PluginFullScreenHolder.java
index ae326d5..42ba7c9 100644
--- a/core/java/android/webkit/PluginFullScreenHolder.java
+++ b/core/java/android/webkit/PluginFullScreenHolder.java
@@ -24,34 +24,44 @@
*/
package android.webkit;
-import android.app.Dialog;
+import android.content.Context;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
-class PluginFullScreenHolder extends Dialog {
+class PluginFullScreenHolder {
private final WebView mWebView;
private final int mNpp;
+ private final int mOrientation;
+
+ // The container for the plugin view
+ private static CustomFrameLayout mLayout;
+
private View mContentView;
- PluginFullScreenHolder(WebView webView, int npp) {
- super(webView.getContext(), android.R.style.Theme_NoTitleBar_Fullscreen);
+ PluginFullScreenHolder(WebView webView, int orientation, int npp) {
mWebView = webView;
mNpp = npp;
+ mOrientation = orientation;
}
- @Override
public void setContentView(View contentView) {
- // as we are sharing the View between full screen and
- // embedded mode, we have to remove the
- // AbsoluteLayout.LayoutParams set by embedded mode to
- // ViewGroup.LayoutParams before adding it to the dialog
- contentView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // Create a FrameLayout that will contain the plugin's view
+ mLayout = new CustomFrameLayout(mWebView.getContext());
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ Gravity.CENTER);
+
+ mLayout.addView(contentView, layoutParams);
+ mLayout.setVisibility(View.VISIBLE);
+
// fixed size is only used either during pinch zoom or surface is too
// big. Make sure it is not fixed size before setting it to the full
// screen content view. The SurfaceView will be set to the correct mode
@@ -62,59 +72,79 @@ class PluginFullScreenHolder extends Dialog {
sView.getHolder().setSizeFromLayout();
}
}
- super.setContentView(contentView);
+
mContentView = contentView;
}
- @Override
- public void onBackPressed() {
- mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN)
- .sendToTarget();
+ public void show() {
+ // Other plugins may attempt to draw so hide them while we're active.
+ if (mWebView.getViewManager() != null)
+ mWebView.getViewManager().hideAll();
+
+ WebChromeClient client = mWebView.getWebChromeClient();
+ client.onShowCustomView(mLayout, mOrientation, mCallback);
}
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyDown(keyCode, event);
- }
- mWebView.onKeyDown(keyCode, event);
- // always return true as we are the handler
- return true;
+ public void hide() {
+ WebChromeClient client = mWebView.getWebChromeClient();
+ client.onHideCustomView();
}
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyUp(keyCode, event);
+ private class CustomFrameLayout extends FrameLayout {
+
+ CustomFrameLayout(Context context) {
+ super(context);
}
- mWebView.onKeyUp(keyCode, event);
- // always return true as we are the handler
- return true;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // always return true as we don't want the event to propagate any further
- return true;
- }
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (event.isSystem()) {
+ return super.onKeyDown(keyCode, event);
+ }
+ mWebView.onKeyDown(keyCode, event);
+ // always return true as we are the handler
+ return true;
+ }
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- mWebView.onTrackballEvent(event);
- // always return true as we are the handler
- return true;
- }
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (event.isSystem()) {
+ return super.onKeyUp(keyCode, event);
+ }
+ mWebView.onKeyUp(keyCode, event);
+ // always return true as we are the handler
+ return true;
+ }
- @Override
- protected void onStop() {
- super.onStop();
- // manually remove the contentView's parent since the dialog does not
- if (mContentView != null && mContentView.getParent() != null) {
- ViewGroup vg = (ViewGroup) mContentView.getParent();
- vg.removeView(mContentView);
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // always return true as we don't want the event to propagate any further
+ return true;
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ mWebView.onTrackballEvent(event);
+ // always return true as we are the handler
+ return true;
}
- mWebView.getWebViewCore().sendMessage(
- WebViewCore.EventHub.HIDE_FULLSCREEN, mNpp, 0);
}
+
+ private final WebChromeClient.CustomViewCallback mCallback =
+ new WebChromeClient.CustomViewCallback() {
+ public void onCustomViewHidden() {
+
+ mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN)
+ .sendToTarget();
+
+ mWebView.getWebViewCore().sendMessage(
+ WebViewCore.EventHub.HIDE_FULLSCREEN, mNpp, 0);
+ mLayout.removeView(mContentView);
+ mLayout = null;
+
+ // Re enable plugin views.
+ mWebView.getViewManager().showAll();
+ }
+ };
}
diff --git a/core/java/android/webkit/SslCertLookupTable.java b/core/java/android/webkit/SslCertLookupTable.java
index abf612e..faff110 100644
--- a/core/java/android/webkit/SslCertLookupTable.java
+++ b/core/java/android/webkit/SslCertLookupTable.java
@@ -19,11 +19,11 @@ package android.webkit;
import android.os.Bundle;
import android.net.http.SslError;
-/*
+/**
* A simple class to store the wrong certificates that user is aware but
* chose to proceed.
*/
-class SslCertLookupTable {
+final class SslCertLookupTable {
private static SslCertLookupTable sTable;
private final Bundle table;
diff --git a/core/java/android/webkit/SslClientCertLookupTable.java b/core/java/android/webkit/SslClientCertLookupTable.java
new file mode 100644
index 0000000..630debd
--- /dev/null
+++ b/core/java/android/webkit/SslClientCertLookupTable.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A simple class to store client certificates that user has chosen.
+ */
+final class SslClientCertLookupTable {
+ private static SslClientCertLookupTable sTable;
+ private final Map<String, byte[]> privateKeys;
+ private final Map<String, byte[][]> certificateChains;
+ private final Set<String> denied;
+
+ public static synchronized SslClientCertLookupTable getInstance() {
+ if (sTable == null) {
+ sTable = new SslClientCertLookupTable();
+ }
+ return sTable;
+ }
+
+ private SslClientCertLookupTable() {
+ privateKeys = new HashMap<String, byte[]>();
+ certificateChains = new HashMap<String, byte[][]>();
+ denied = new HashSet<String>();
+ }
+
+ public void Allow(String host_and_port, byte[] privateKey, byte[][] chain) {
+ privateKeys.put(host_and_port, privateKey);
+ certificateChains.put(host_and_port, chain);
+ denied.remove(host_and_port);
+ }
+
+ public void Deny(String host_and_port) {
+ privateKeys.remove(host_and_port);
+ certificateChains.remove(host_and_port);
+ denied.add(host_and_port);
+ }
+
+ public boolean IsAllowed(String host_and_port) {
+ return privateKeys.containsKey(host_and_port);
+ }
+
+ public boolean IsDenied(String host_and_port) {
+ return denied.contains(host_and_port);
+ }
+
+ public byte[] PrivateKey(String host_and_port) {
+ return privateKeys.get(host_and_port);
+ }
+
+ public byte[][] CertificateChain(String host_and_port) {
+ return certificateChains.get(host_and_port);
+ }
+}
diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java
new file mode 100644
index 0000000..0fc76fa
--- /dev/null
+++ b/core/java/android/webkit/ViewStateSerializer.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.webkit;
+
+import android.graphics.Point;
+import android.graphics.Region;
+import android.webkit.WebViewCore.DrawData;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @hide
+ */
+class ViewStateSerializer {
+
+ private static final int WORKING_STREAM_STORAGE = 16 * 1024;
+
+ static final int VERSION = 1;
+
+ static boolean serializeViewState(OutputStream stream, WebView web)
+ throws IOException {
+ DataOutputStream dos = new DataOutputStream(stream);
+ dos.writeInt(VERSION);
+ dos.writeInt(web.getContentWidth());
+ dos.writeInt(web.getContentHeight());
+ return nativeSerializeViewState(web.getBaseLayer(), dos,
+ new byte[WORKING_STREAM_STORAGE]);
+ }
+
+ static DrawData deserializeViewState(InputStream stream, WebView web)
+ throws IOException {
+ DataInputStream dis = new DataInputStream(stream);
+ int version = dis.readInt();
+ if (version != VERSION) {
+ throw new IOException("Unexpected version: " + version);
+ }
+ int contentWidth = dis.readInt();
+ int contentHeight = dis.readInt();
+ int baseLayer = nativeDeserializeViewState(dis,
+ new byte[WORKING_STREAM_STORAGE]);
+
+ final WebViewCore.DrawData draw = new WebViewCore.DrawData();
+ draw.mViewState = new WebViewCore.ViewState();
+ int viewWidth = web.getViewWidth();
+ int viewHeight = web.getViewHeightWithTitle() - web.getTitleHeight();
+ draw.mViewSize = new Point(viewWidth, viewHeight);
+ draw.mContentSize = new Point(contentWidth, contentHeight);
+ draw.mViewState.mDefaultScale = web.getDefaultZoomScale();
+ draw.mBaseLayer = baseLayer;
+ draw.mInvalRegion = new Region(0, 0, contentWidth, contentHeight);
+ return draw;
+ }
+
+ private static native boolean nativeSerializeViewState(int baseLayer,
+ OutputStream stream, byte[] storage);
+
+ // Returns a pointer to the BaseLayer
+ private static native int nativeDeserializeViewState(
+ InputStream stream, byte[] storage);
+
+ private ViewStateSerializer() {}
+}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 755366c..ae40ded 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Message;
@@ -77,6 +78,18 @@ public class WebChromeClient {
/**
* Notify the host application that the current page would
+ * like to show a custom View in a particular orientation.
+ * @param view is the View object to be shown.
+ * @param requestedOrientation An orientation constant as used in
+ * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+ * @param callback is the callback to be invoked if and when the view
+ * is dismissed.
+ */
+ public void onShowCustomView(View view, int requestedOrientation,
+ CustomViewCallback callback) {};
+
+ /**
+ * Notify the host application that the current page would
* like to hide its custom view.
*/
public void onHideCustomView() {}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 8e57260..c361a4a 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -183,7 +183,6 @@ public class WebSettings {
private boolean mJavaScriptCanOpenWindowsAutomatically = false;
private boolean mUseDoubleTree = false;
private boolean mUseWideViewport = false;
- private boolean mUseFixedViewport = false;
private boolean mSupportMultipleWindows = false;
private boolean mShrinksStandaloneImagesToFit = false;
private long mMaximumDecodedImageSize = 0; // 0 means default
@@ -223,6 +222,7 @@ public class WebSettings {
private boolean mAllowContentAccess = true;
private boolean mLoadWithOverviewMode = false;
private boolean mEnableSmoothTransition = false;
+ private boolean mForceUserScalable = false;
// AutoFill Profile data
/**
@@ -381,13 +381,6 @@ public class WebSettings {
mDefaultTextEncoding = context.getString(com.android.internal.
R.string.default_text_encoding);
- // Detect tablet device for fixed viewport mode.
- final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
- final int landscapeWidth = Math.max(metrics.widthPixels, metrics.heightPixels);
- final int minTabletWidth = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.min_xlarge_screen_width);
- mUseFixedViewport = (metrics.density == 1.0f && landscapeWidth >= minTabletWidth);
-
if (sLockForLocaleSettings == null) {
sLockForLocaleSettings = new Object();
sLocale = Locale.getDefault();
@@ -1650,11 +1643,11 @@ public class WebSettings {
}
/**
- * Returns whether to use fixed viewport. Fixed viewport should operate only
- * when wide viewport is on.
+ * Returns whether to use fixed viewport. Use fixed viewport
+ * whenever wide viewport is on.
*/
/* package */ boolean getUseFixedViewport() {
- return getUseWideViewPort() && mUseFixedViewport;
+ return getUseWideViewPort();
}
/**
@@ -1680,6 +1673,23 @@ public class WebSettings {
}
}
+ /**
+ * Returns whether the viewport metatag can disable zooming
+ * @hide
+ */
+ public boolean forceUserScalable() {
+ return mForceUserScalable;
+ }
+
+ /**
+ * Sets whether viewport metatag can disable zooming.
+ * @param flag Whether or not to forceably enable user scalable.
+ * @hide
+ */
+ public synchronized void setForceUserScalable(boolean flag) {
+ mForceUserScalable = flag;
+ }
+
synchronized void setSyntheticLinksEnabled(boolean flag) {
if (mSyntheticLinksEnabled != flag) {
mSyntheticLinksEnabled = flag;
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 26cfbff..d54230c 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -34,6 +34,7 @@ import android.text.BoringLayout.Metrics;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.InputFilter;
+import android.text.InputType;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
@@ -51,8 +52,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@@ -515,7 +516,6 @@ import junit.framework.Assert;
int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
}
- updateCursorControllerPositions();
}
@Override
@@ -854,7 +854,7 @@ import junit.framework.Assert;
public void setAdapterCustom(AutoCompleteAdapter adapter) {
if (adapter != null) {
setInputType(getInputType()
- | EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+ | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
adapter.setTextView(this);
if (mAutoFillable) {
setOnItemClickListener(this);
@@ -935,7 +935,7 @@ import junit.framework.Assert;
*/
/* package */ void setInPassword(boolean inPassword) {
if (inPassword) {
- setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.
+ setInputType(InputType.TYPE_CLASS_TEXT | EditorInfo.
TYPE_TEXT_VARIATION_WEB_PASSWORD);
createBackground();
}
@@ -1147,8 +1147,8 @@ import junit.framework.Assert;
boolean single = true;
boolean inPassword = false;
int maxLength = -1;
- int inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
+ int inputType = InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
| EditorInfo.IME_FLAG_NO_FULLSCREEN;
if (TEXT_AREA != type
@@ -1161,9 +1161,9 @@ import junit.framework.Assert;
break;
case TEXT_AREA:
single = false;
- inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
- | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
- | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
+ inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE
+ | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
+ | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
imeOptions |= EditorInfo.IME_ACTION_NONE;
break;
case PASSWORD:
@@ -1175,20 +1175,20 @@ import junit.framework.Assert;
break;
case EMAIL:
// inputType needs to be overwritten because of the different text variation.
- inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+ inputType = InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case NUMBER:
// inputType needs to be overwritten because of the different class.
- inputType = EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_NORMAL;
+ inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL;
// Number and telephone do not have both a Tab key and an
// action, so set the action to NEXT
imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case TELEPHONE:
// inputType needs to be overwritten because of the different class.
- inputType = EditorInfo.TYPE_CLASS_PHONE;
+ inputType = InputType.TYPE_CLASS_PHONE;
imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case URL:
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index eeb5b7b..1014d7e 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -27,6 +27,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
@@ -43,7 +44,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
-import android.graphics.SurfaceTexture;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.net.Proxy;
@@ -53,7 +53,9 @@ import android.net.http.SslCertificate;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.StrictMode;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.text.Selection;
@@ -81,6 +83,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebTextView.AutoCompleteAdapter;
+import android.webkit.WebViewCore.DrawData;
import android.webkit.WebViewCore.EventHub;
import android.webkit.WebViewCore.TouchEventData;
import android.webkit.WebViewCore.TouchHighlightData;
@@ -95,10 +98,15 @@ import android.widget.ListView;
import android.widget.OverScroller;
import android.widget.Toast;
+import junit.framework.Assert;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
@@ -110,8 +118,6 @@ import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import junit.framework.Assert;
-
/**
* <p>A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
@@ -166,7 +172,7 @@ import junit.framework.Assert;
*
* // OR, you can also load from an HTML string:
* String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
- * webview.loadData(summary, "text/html", "utf-8");
+ * webview.loadData(summary, "text/html", null);
* // ... although note that there are restrictions on what this HTML can do.
* // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
* #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
@@ -613,6 +619,9 @@ public class WebView extends AbsoluteLayout
// SetBaseLayer time and to pause when WebView paused.
private HTML5VideoViewProxy mHTML5VideoViewProxy;
+ // If we are using a set picture, don't send view updates to webkit
+ private boolean mBlockWebkitViewMessages = false;
+
/*
* Private message ids
*/
@@ -821,6 +830,8 @@ public class WebView extends AbsoluteLayout
private WebViewCore.AutoFillData mAutoFillData;
+ private static boolean sNotificationsEnabled = true;
+
/**
* URI scheme for telephone number
*/
@@ -874,8 +885,9 @@ public class WebView extends AbsoluteLayout
*/
public static final int UNKNOWN_TYPE = 0;
/**
- * HitTestResult for hitting a HTML::a tag
+ * @deprecated This type is no longer used.
*/
+ @Deprecated
public static final int ANCHOR_TYPE = 1;
/**
* HitTestResult for hitting a phone number
@@ -894,8 +906,9 @@ public class WebView extends AbsoluteLayout
*/
public static final int IMAGE_TYPE = 5;
/**
- * HitTestResult for hitting a HTML::a tag which contains HTML::img
+ * @deprecated This type is no longer used.
*/
+ @Deprecated
public static final int IMAGE_ANCHOR_TYPE = 6;
/**
* HitTestResult for hitting a HTML::a tag with src=http
@@ -987,6 +1000,7 @@ public class WebView extends AbsoluteLayout
protected WebView(Context context, AttributeSet attrs, int defStyle,
Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
super(context, attrs, defStyle);
+ checkThread();
// Used by the chrome stack to find application paths
JniUtil.setContext(context);
@@ -1024,24 +1038,38 @@ public class WebView extends AbsoluteLayout
}
/*
- * A variable to track if there is a receiver added for PROXY_CHANGE_ACTION
+ * Receiver for PROXY_CHANGE_ACTION, will be null when it is not added handling broadcasts.
*/
- private static boolean sProxyReceiverAdded;
+ private static ProxyReceiver sProxyReceiver;
+ /*
+ * @param context This method expects this to be a valid context
+ */
private static synchronized void setupProxyListener(Context context) {
- if (sProxyReceiverAdded) {
+ if (sProxyReceiver != null || sNotificationsEnabled == false) {
return;
}
IntentFilter filter = new IntentFilter();
filter.addAction(Proxy.PROXY_CHANGE_ACTION);
+ sProxyReceiver = new ProxyReceiver();
Intent currentProxy = context.getApplicationContext().registerReceiver(
- new ProxyReceiver(), filter);
- sProxyReceiverAdded = true;
+ sProxyReceiver, filter);
if (currentProxy != null) {
handleProxyBroadcast(currentProxy);
}
}
+ /*
+ * @param context This method expects this to be a valid context
+ */
+ private static synchronized void disableProxyListener(Context context) {
+ if (sProxyReceiver == null)
+ return;
+
+ context.getApplicationContext().unregisterReceiver(sProxyReceiver);
+ sProxyReceiver = null;
+ }
+
private static void handleProxyBroadcast(Intent intent) {
ProxyProperties proxyProperties = (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO);
if (proxyProperties == null || proxyProperties.getHost() == null) {
@@ -1129,7 +1157,7 @@ public class WebView extends AbsoluteLayout
PackageInfo pInfo = pm.getPackageInfo(name,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
installedPackages.add(name);
- } catch(PackageManager.NameNotFoundException e) {
+ } catch (PackageManager.NameNotFoundException e) {
// package not found
}
}
@@ -1184,6 +1212,11 @@ public class WebView extends AbsoluteLayout
mHTML5VideoViewProxy = null ;
}
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return true;
+ }
+
/**
* Adds accessibility APIs to JavaScript.
*
@@ -1300,6 +1333,7 @@ public class WebView extends AbsoluteLayout
* @param overlay TRUE if horizontal scrollbar should have overlay style.
*/
public void setHorizontalScrollbarOverlay(boolean overlay) {
+ checkThread();
mOverlayHorizontalScrollbar = overlay;
}
@@ -1308,6 +1342,7 @@ public class WebView extends AbsoluteLayout
* @param overlay TRUE if vertical scrollbar should have overlay style.
*/
public void setVerticalScrollbarOverlay(boolean overlay) {
+ checkThread();
mOverlayVerticalScrollbar = overlay;
}
@@ -1316,6 +1351,7 @@ public class WebView extends AbsoluteLayout
* @return TRUE if horizontal scrollbar has overlay style.
*/
public boolean overlayHorizontalScrollbar() {
+ checkThread();
return mOverlayHorizontalScrollbar;
}
@@ -1324,6 +1360,7 @@ public class WebView extends AbsoluteLayout
* @return TRUE if vertical scrollbar has overlay style.
*/
public boolean overlayVerticalScrollbar() {
+ checkThread();
return mOverlayVerticalScrollbar;
}
@@ -1355,6 +1392,7 @@ public class WebView extends AbsoluteLayout
* @deprecated This method is now obsolete.
*/
public int getVisibleTitleHeight() {
+ checkThread();
// need to restrict mScrollY due to over scroll
return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
}
@@ -1368,7 +1406,7 @@ public class WebView extends AbsoluteLayout
return getViewHeightWithTitle() - getVisibleTitleHeight();
}
- private int getViewHeightWithTitle() {
+ int getViewHeightWithTitle() {
int height = getHeight();
if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
height -= getHorizontalScrollbarHeight();
@@ -1381,6 +1419,7 @@ public class WebView extends AbsoluteLayout
* there is no certificate (the site is not secure).
*/
public SslCertificate getCertificate() {
+ checkThread();
return mCertificate;
}
@@ -1388,6 +1427,7 @@ public class WebView extends AbsoluteLayout
* Sets the SSL certificate for the main top-level page.
*/
public void setCertificate(SslCertificate certificate) {
+ checkThread();
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "setCertificate=" + certificate);
}
@@ -1407,6 +1447,7 @@ public class WebView extends AbsoluteLayout
* @param password The password for the given host.
*/
public void savePassword(String host, String username, String password) {
+ checkThread();
mDatabase.setUsernamePassword(host, username, password);
}
@@ -1421,6 +1462,7 @@ public class WebView extends AbsoluteLayout
*/
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
+ checkThread();
mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
}
@@ -1434,6 +1476,7 @@ public class WebView extends AbsoluteLayout
* String[1] is password. Return null if it can't find anything.
*/
public String[] getHttpAuthUsernamePassword(String host, String realm) {
+ checkThread();
return mDatabase.getHttpAuthUsernamePassword(host, realm);
}
@@ -1466,6 +1509,11 @@ public class WebView extends AbsoluteLayout
* methods may be called on a WebView after destroy.
*/
public void destroy() {
+ checkThread();
+ destroyImpl();
+ }
+
+ private void destroyImpl() {
clearHelpers();
if (mListBoxDialog != null) {
mListBoxDialog.dismiss();
@@ -1500,21 +1548,38 @@ public class WebView extends AbsoluteLayout
/**
* Enables platform notifications of data state and proxy changes.
- * @deprecated Obsolete - platform notifications are always enabled.
+ * Notifications are enabled by default.
+ *
+ * @deprecated This method is now obsolete.
*/
@Deprecated
public static void enablePlatformNotifications() {
- Network.enablePlatformNotifications();
+ checkThread();
+ synchronized (WebView.class) {
+ Network.enablePlatformNotifications();
+ sNotificationsEnabled = true;
+ Context context = JniUtil.getContext();
+ if (context != null)
+ setupProxyListener(context);
+ }
}
/**
- * If platform notifications are enabled, this should be called
- * from the Activity's onPause() or onStop().
- * @deprecated Obsolete - platform notifications are always enabled.
+ * Disables platform notifications of data state and proxy changes.
+ * Notifications are enabled by default.
+ *
+ * @deprecated This method is now obsolete.
*/
@Deprecated
public static void disablePlatformNotifications() {
- Network.disablePlatformNotifications();
+ checkThread();
+ synchronized (WebView.class) {
+ Network.disablePlatformNotifications();
+ sNotificationsEnabled = false;
+ Context context = JniUtil.getContext();
+ if (context != null)
+ disableProxyListener(context);
+ }
}
/**
@@ -1525,6 +1590,7 @@ public class WebView extends AbsoluteLayout
* @hide pending API solidification
*/
public void setJsFlags(String flags) {
+ checkThread();
mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
}
@@ -1535,6 +1601,7 @@ public class WebView extends AbsoluteLayout
* @param networkUp boolean indicating if network is available
*/
public void setNetworkAvailable(boolean networkUp) {
+ checkThread();
mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
networkUp ? 1 : 0, 0);
}
@@ -1544,6 +1611,7 @@ public class WebView extends AbsoluteLayout
* {@hide}
*/
public void setNetworkType(String type, String subtype) {
+ checkThread();
Map<String, String> map = new HashMap<String, String>();
map.put("type", type);
map.put("subtype", subtype);
@@ -1563,6 +1631,7 @@ public class WebView extends AbsoluteLayout
* @see #restorePicture
*/
public WebBackForwardList saveState(Bundle outState) {
+ checkThread();
if (outState == null) {
return null;
}
@@ -1619,6 +1688,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public boolean savePicture(Bundle b, final File dest) {
+ checkThread();
if (dest == null || b == null) {
return false;
}
@@ -1683,6 +1753,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public boolean restorePicture(Bundle b, File src) {
+ checkThread();
if (src == null || b == null) {
return false;
}
@@ -1721,6 +1792,53 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Saves the view data to the output stream. The output is highly
+ * version specific, and may not be able to be loaded by newer versions
+ * of WebView.
+ * @param stream The {@link OutputStream} to save to
+ * @return True if saved successfully
+ * @hide
+ */
+ public boolean saveViewState(OutputStream stream) {
+ try {
+ return ViewStateSerializer.serializeViewState(stream, this);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Failed to saveViewState", e);
+ }
+ return false;
+ }
+
+ /**
+ * Loads the view data from the input stream. See
+ * {@link #saveViewState(OutputStream)} for more information.
+ * @param stream The {@link InputStream} to load from
+ * @return True if loaded successfully
+ * @hide
+ */
+ public boolean loadViewState(InputStream stream) {
+ try {
+ mLoadedPicture = ViewStateSerializer.deserializeViewState(stream, this);
+ mBlockWebkitViewMessages = true;
+ setNewPicture(mLoadedPicture, true);
+ return true;
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Failed to loadViewState", e);
+ }
+ return false;
+ }
+
+ /**
+ * Clears the view state set with {@link #loadViewState(InputStream)}.
+ * This WebView will then switch to showing the content from webkit
+ * @hide
+ */
+ public void clearViewState() {
+ mBlockWebkitViewMessages = false;
+ mLoadedPicture = null;
+ invalidate();
+ }
+
+ /**
* Restore the state of this WebView from the given map used in
* {@link android.app.Activity#onRestoreInstanceState}. This method should
* be called to restore the state of the WebView before using the object. If
@@ -1735,6 +1853,7 @@ public class WebView extends AbsoluteLayout
* @see #restorePicture
*/
public WebBackForwardList restoreState(Bundle inState) {
+ checkThread();
WebBackForwardList returnList = null;
if (inState == null) {
return returnList;
@@ -1794,6 +1913,11 @@ public class WebView extends AbsoluteLayout
* will be replaced by the intrinsic value of the WebView.
*/
public void loadUrl(String url, Map<String, String> extraHeaders) {
+ checkThread();
+ loadUrlImpl(url, extraHeaders);
+ }
+
+ private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
switchOutDrawHistory();
WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
arg.mUrl = url;
@@ -1807,10 +1931,15 @@ public class WebView extends AbsoluteLayout
* @param url The url of the resource to load.
*/
public void loadUrl(String url) {
+ checkThread();
+ loadUrlImpl(url);
+ }
+
+ private void loadUrlImpl(String url) {
if (url == null) {
return;
}
- loadUrl(url, null);
+ loadUrlImpl(url, null);
}
/**
@@ -1822,6 +1951,7 @@ public class WebView extends AbsoluteLayout
* @param postData The data will be passed to "POST" request.
*/
public void postUrl(String url, byte[] postData) {
+ checkThread();
if (URLUtil.isNetworkUrl(url)) {
switchOutDrawHistory();
WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
@@ -1830,22 +1960,37 @@ public class WebView extends AbsoluteLayout
mWebViewCore.sendMessage(EventHub.POST_URL, arg);
clearHelpers();
} else {
- loadUrl(url);
+ loadUrlImpl(url);
}
}
/**
- * Load the given data into the WebView. This will load the data into
- * WebView using the data: scheme. Content loaded through this mechanism
- * does not have the ability to load content from the network.
- * @param data A String of data in the given encoding. The date must
- * be URI-escaped -- '#', '%', '\', '?' should be replaced by %23, %25,
- * %27, %3f respectively.
- * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg
- * @param encoding The encoding of the data. i.e. utf-8, base64
+ * Load the given data into the WebView using a 'data' scheme URL. Content
+ * loaded in this way does not have the ability to load content from the
+ * network.
+ * <p>
+ * If the value of the encoding parameter is 'base64', then the data must
+ * be encoded as base64. Otherwise, the data must use ASCII encoding for
+ * octets inside the range of safe URL characters and use the standard %xx
+ * hex encoding of URLs for octets outside that range.
+ * @param data A String of data in the given encoding.
+ * @param mimeType The MIMEType of the data, e.g. 'text/html'.
+ * @param encoding The encoding of the data.
*/
public void loadData(String data, String mimeType, String encoding) {
- loadUrl("data:" + mimeType + ";" + encoding + "," + data);
+ checkThread();
+ loadDataImpl(data, mimeType, encoding);
+ }
+
+ private void loadDataImpl(String data, String mimeType, String encoding) {
+ StringBuilder dataUrl = new StringBuilder("data:");
+ dataUrl.append(mimeType);
+ if ("base64".equals(encoding)) {
+ dataUrl.append(";base64");
+ }
+ dataUrl.append(",");
+ dataUrl.append(data);
+ loadUrlImpl(dataUrl.toString());
}
/**
@@ -1854,13 +1999,9 @@ public class WebView extends AbsoluteLayout
* that is loaded through this interface. As such, it is used to resolve any
* relative URLs. The historyUrl is used for the history entry.
* <p>
- * Note for post 1.0. Due to the change in the WebKit, the access to asset
- * files through "file:///android_asset/" for the sub resources is more
- * restricted. If you provide null or empty string as baseUrl, you won't be
- * able to access asset files. If the baseUrl is anything other than
- * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
- * sub resources.
- *
+ * Note that content specified in this way can access local device files
+ * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
+ * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
* @param baseUrl Url to resolve relative paths with, if null defaults to
* "about:blank"
* @param data A String of data in the given encoding.
@@ -1871,9 +2012,10 @@ public class WebView extends AbsoluteLayout
*/
public void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl) {
+ checkThread();
if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
- loadData(data, mimeType, encoding);
+ loadDataImpl(data, mimeType, encoding);
return;
}
switchOutDrawHistory();
@@ -1893,7 +2035,8 @@ public class WebView extends AbsoluteLayout
* @param filename The filename where the archive should be placed.
*/
public void saveWebArchive(String filename) {
- saveWebArchive(filename, false, null);
+ checkThread();
+ saveWebArchiveImpl(filename, false, null);
}
/* package */ static class SaveWebArchiveMessage {
@@ -1922,6 +2065,12 @@ public class WebView extends AbsoluteLayout
* file failed.
*/
public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
+ checkThread();
+ saveWebArchiveImpl(basename, autoname, callback);
+ }
+
+ private void saveWebArchiveImpl(String basename, boolean autoname,
+ ValueCallback<String> callback) {
mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
new SaveWebArchiveMessage(basename, autoname, callback));
}
@@ -1930,6 +2079,7 @@ public class WebView extends AbsoluteLayout
* Stop the current load.
*/
public void stopLoading() {
+ checkThread();
// TODO: should we clear all the messages in the queue before sending
// STOP_LOADING?
switchOutDrawHistory();
@@ -1940,6 +2090,7 @@ public class WebView extends AbsoluteLayout
* Reload the current url.
*/
public void reload() {
+ checkThread();
clearHelpers();
switchOutDrawHistory();
mWebViewCore.sendMessage(EventHub.RELOAD);
@@ -1950,6 +2101,7 @@ public class WebView extends AbsoluteLayout
* @return True iff this WebView has a back history item.
*/
public boolean canGoBack() {
+ checkThread();
WebBackForwardList l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
@@ -1964,7 +2116,8 @@ public class WebView extends AbsoluteLayout
* Go back in the history of this WebView.
*/
public void goBack() {
- goBackOrForward(-1);
+ checkThread();
+ goBackOrForwardImpl(-1);
}
/**
@@ -1972,6 +2125,7 @@ public class WebView extends AbsoluteLayout
* @return True iff this Webview has a forward history item.
*/
public boolean canGoForward() {
+ checkThread();
WebBackForwardList l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
@@ -1986,7 +2140,8 @@ public class WebView extends AbsoluteLayout
* Go forward in the history of this WebView.
*/
public void goForward() {
- goBackOrForward(1);
+ checkThread();
+ goBackOrForwardImpl(1);
}
/**
@@ -1996,6 +2151,7 @@ public class WebView extends AbsoluteLayout
* history.
*/
public boolean canGoBackOrForward(int steps) {
+ checkThread();
WebBackForwardList l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
@@ -2015,6 +2171,11 @@ public class WebView extends AbsoluteLayout
* forward list.
*/
public void goBackOrForward(int steps) {
+ checkThread();
+ goBackOrForwardImpl(steps);
+ }
+
+ private void goBackOrForwardImpl(int steps) {
goBackOrForward(steps, false);
}
@@ -2030,6 +2191,7 @@ public class WebView extends AbsoluteLayout
* Returns true if private browsing is enabled in this WebView.
*/
public boolean isPrivateBrowsingEnabled() {
+ checkThread();
return getSettings().isPrivateBrowsingEnabled();
}
@@ -2052,6 +2214,7 @@ public class WebView extends AbsoluteLayout
* @return true if the page was scrolled
*/
public boolean pageUp(boolean top) {
+ checkThread();
if (mNativeClass == 0) {
return false;
}
@@ -2078,6 +2241,7 @@ public class WebView extends AbsoluteLayout
* @return true if the page was scrolled
*/
public boolean pageDown(boolean bottom) {
+ checkThread();
if (mNativeClass == 0) {
return false;
}
@@ -2102,6 +2266,7 @@ public class WebView extends AbsoluteLayout
* and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY
*/
public void clearView() {
+ checkThread();
mContentWidth = 0;
mContentHeight = 0;
setBaseLayer(0, null, false, false);
@@ -2118,6 +2283,7 @@ public class WebView extends AbsoluteLayout
* bounds of the view.
*/
public Picture capturePicture() {
+ checkThread();
if (mNativeClass == 0) return null;
Picture result = new Picture();
nativeCopyBaseContentToPicture(result);
@@ -2148,6 +2314,7 @@ public class WebView extends AbsoluteLayout
* @return The current scale.
*/
public float getScale() {
+ checkThread();
return mZoomManager.getScale();
}
@@ -2160,6 +2327,7 @@ public class WebView extends AbsoluteLayout
* @param scaleInPercent The initial scale in percent.
*/
public void setInitialScale(int scaleInPercent) {
+ checkThread();
mZoomManager.setInitialScaleInPercent(scaleInPercent);
}
@@ -2169,6 +2337,7 @@ public class WebView extends AbsoluteLayout
* level of this WebView.
*/
public void invokeZoomPicker() {
+ checkThread();
if (!getSettings().supportZoom()) {
Log.w(LOGTAG, "This WebView doesn't support zoom.");
return;
@@ -2196,6 +2365,7 @@ public class WebView extends AbsoluteLayout
* HitTestResult type is set to UNKNOWN_TYPE.
*/
public HitTestResult getHitTestResult() {
+ checkThread();
return hitTestResult(mInitialHitTestResult);
}
@@ -2277,6 +2447,7 @@ public class WebView extends AbsoluteLayout
* - "src" returns the image's src attribute.
*/
public void requestFocusNodeHref(Message hrefMsg) {
+ checkThread();
if (hrefMsg == null) {
return;
}
@@ -2305,6 +2476,7 @@ public class WebView extends AbsoluteLayout
* as the data member with "url" as key. The result can be null.
*/
public void requestImageRef(Message msg) {
+ checkThread();
if (0 == mNativeClass) return; // client isn't initialized
int contentX = viewToContentX(mLastTouchX + mScrollX);
int contentY = viewToContentY(mLastTouchY + mScrollY);
@@ -2383,6 +2555,8 @@ public class WebView extends AbsoluteLayout
*/
public void setTitleBarGravity(int gravity) {
mTitleGravity = gravity;
+ // force refresh
+ invalidate();
}
/**
@@ -2549,10 +2723,12 @@ public class WebView extends AbsoluteLayout
calcOurContentVisibleRect(rect);
// Rect.equals() checks for null input.
if (!rect.equals(mLastVisibleRectSent)) {
- Point pos = new Point(rect.left, rect.top);
- mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
- mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
- nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, pos);
+ if (!mBlockWebkitViewMessages) {
+ Point pos = new Point(rect.left, rect.top);
+ mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
+ mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
+ nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, pos);
+ }
mLastVisibleRectSent = rect;
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
}
@@ -2567,7 +2743,9 @@ public class WebView extends AbsoluteLayout
// TODO: the global offset is only used by windowRect()
// in ChromeClientAndroid ; other clients such as touch
// and mouse events could return view + screen relative points.
- mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
+ if (!mBlockWebkitViewMessages) {
+ mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
+ }
mLastGlobalRect = globalRect;
}
return rect;
@@ -2632,6 +2810,7 @@ public class WebView extends AbsoluteLayout
* @return true if new values were sent
*/
boolean sendViewSizeZoom(boolean force) {
+ if (mBlockWebkitViewMessages) return false;
if (mZoomManager.isPreventingWebkitUpdates()) return false;
int viewWidth = getViewWidth();
@@ -2799,6 +2978,7 @@ public class WebView extends AbsoluteLayout
* @return The url for the current page.
*/
public String getUrl() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getUrl() : null;
}
@@ -2812,6 +2992,7 @@ public class WebView extends AbsoluteLayout
* @return The url that was originally requested for the current page.
*/
public String getOriginalUrl() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getOriginalUrl() : null;
}
@@ -2822,6 +3003,7 @@ public class WebView extends AbsoluteLayout
* @return The title for the current page.
*/
public String getTitle() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getTitle() : null;
}
@@ -2832,6 +3014,7 @@ public class WebView extends AbsoluteLayout
* @return The favicon for the current page.
*/
public Bitmap getFavicon() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getFavicon() : null;
}
@@ -2852,6 +3035,7 @@ public class WebView extends AbsoluteLayout
* @return The progress for the current page between 0 and 100.
*/
public int getProgress() {
+ checkThread();
return mCallbackProxy.getProgress();
}
@@ -2859,6 +3043,7 @@ public class WebView extends AbsoluteLayout
* @return the height of the HTML content.
*/
public int getContentHeight() {
+ checkThread();
return mContentHeight;
}
@@ -2876,6 +3061,7 @@ public class WebView extends AbsoluteLayout
* useful if the application has been paused.
*/
public void pauseTimers() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
}
@@ -2884,6 +3070,7 @@ public class WebView extends AbsoluteLayout
* This will resume dispatching all timers.
*/
public void resumeTimers() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
}
@@ -2896,6 +3083,7 @@ public class WebView extends AbsoluteLayout
* Note that this differs from pauseTimers(), which affects all WebViews.
*/
public void onPause() {
+ checkThread();
if (!mIsPaused) {
mIsPaused = true;
mWebViewCore.sendMessage(EventHub.ON_PAUSE);
@@ -2911,6 +3099,7 @@ public class WebView extends AbsoluteLayout
* Call this to resume a WebView after a previous call to onPause().
*/
public void onResume() {
+ checkThread();
if (mIsPaused) {
mIsPaused = false;
mWebViewCore.sendMessage(EventHub.ON_RESUME);
@@ -2931,6 +3120,7 @@ public class WebView extends AbsoluteLayout
* free any available memory.
*/
public void freeMemory() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
}
@@ -2941,6 +3131,7 @@ public class WebView extends AbsoluteLayout
* @param includeDiskFiles If false, only the RAM cache is cleared.
*/
public void clearCache(boolean includeDiskFiles) {
+ checkThread();
// Note: this really needs to be a static method as it clears cache for all
// WebView. But we need mWebViewCore to send message to WebCore thread, so
// we can't make this static.
@@ -2953,6 +3144,7 @@ public class WebView extends AbsoluteLayout
* currently focused textfield if there is one.
*/
public void clearFormData() {
+ checkThread();
if (inEditingMode()) {
AutoCompleteAdapter adapter = null;
mWebTextView.setAdapterCustom(adapter);
@@ -2963,6 +3155,7 @@ public class WebView extends AbsoluteLayout
* Tell the WebView to clear its internal back/forward list.
*/
public void clearHistory() {
+ checkThread();
mCallbackProxy.getBackForwardList().setClearPending();
mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
}
@@ -2972,6 +3165,7 @@ public class WebView extends AbsoluteLayout
* certificate errors.
*/
public void clearSslPreferences() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
}
@@ -2984,6 +3178,7 @@ public class WebView extends AbsoluteLayout
* updated to reflect any new state.
*/
public WebBackForwardList copyBackForwardList() {
+ checkThread();
return mCallbackProxy.getBackForwardList().clone();
}
@@ -2995,6 +3190,7 @@ public class WebView extends AbsoluteLayout
* @param forward Direction to search.
*/
public void findNext(boolean forward) {
+ checkThread();
if (0 == mNativeClass) return; // client isn't initialized
nativeFindNext(forward);
}
@@ -3006,6 +3202,7 @@ public class WebView extends AbsoluteLayout
* that were found.
*/
public int findAll(String find) {
+ checkThread();
if (0 == mNativeClass) return 0; // client isn't initialized
int result = find != null ? nativeFindAll(find.toLowerCase(),
find.toUpperCase(), find.equalsIgnoreCase(mLastFind)) : 0;
@@ -3025,6 +3222,7 @@ public class WebView extends AbsoluteLayout
* @return boolean True if the find dialog is shown, false otherwise.
*/
public boolean showFindDialog(String text, boolean showIme) {
+ checkThread();
FindActionModeCallback callback = new FindActionModeCallback(mContext);
if (getParent() == null || startActionMode(callback) == null) {
// Could not start the action mode, so end Find on page
@@ -3101,6 +3299,7 @@ public class WebView extends AbsoluteLayout
* @return the address, or if no address is found, return null.
*/
public static String findAddress(String addr) {
+ checkThread();
return findAddress(addr, false);
}
@@ -3134,6 +3333,7 @@ public class WebView extends AbsoluteLayout
* Clear the highlighting surrounding text matches created by findAll.
*/
public void clearMatches() {
+ checkThread();
if (mNativeClass == 0)
return;
nativeSetFindIsEmpty();
@@ -3163,6 +3363,7 @@ public class WebView extends AbsoluteLayout
* @param response The message that will be dispatched with the result.
*/
public void documentHasImages(Message response) {
+ checkThread();
if (response == null) {
return;
}
@@ -3222,9 +3423,11 @@ public class WebView extends AbsoluteLayout
}
abortAnimation();
mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
- WebViewCore.resumePriority();
- if (!mSelectingText) {
- WebViewCore.resumeUpdatePicture(mWebViewCore);
+ if (!mBlockWebkitViewMessages) {
+ WebViewCore.resumePriority();
+ if (!mSelectingText) {
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
+ }
}
if (oldX != mScrollX || oldY != mScrollY) {
sendOurVisibleRect();
@@ -3558,6 +3761,7 @@ public class WebView extends AbsoluteLayout
* @param client An implementation of WebViewClient.
*/
public void setWebViewClient(WebViewClient client) {
+ checkThread();
mCallbackProxy.setWebViewClient(client);
}
@@ -3578,6 +3782,7 @@ public class WebView extends AbsoluteLayout
* @param listener An implementation of DownloadListener.
*/
public void setDownloadListener(DownloadListener listener) {
+ checkThread();
mCallbackProxy.setDownloadListener(listener);
}
@@ -3588,6 +3793,7 @@ public class WebView extends AbsoluteLayout
* @param client An implementation of WebChromeClient.
*/
public void setWebChromeClient(WebChromeClient client) {
+ checkThread();
mCallbackProxy.setWebChromeClient(client);
}
@@ -3628,6 +3834,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public void setPictureListener(PictureListener listener) {
+ checkThread();
mPictureListener = listener;
}
@@ -3669,6 +3876,7 @@ public class WebView extends AbsoluteLayout
* JavaScript.
*/
public void addJavascriptInterface(Object obj, String interfaceName) {
+ checkThread();
if (obj == null) {
return;
}
@@ -3683,6 +3891,7 @@ public class WebView extends AbsoluteLayout
* @param interfaceName The name of the interface to remove.
*/
public void removeJavascriptInterface(String interfaceName) {
+ checkThread();
if (mWebViewCore != null) {
WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
arg.mInterfaceName = interfaceName;
@@ -3697,6 +3906,7 @@ public class WebView extends AbsoluteLayout
* settings.
*/
public WebSettings getSettings() {
+ checkThread();
return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
}
@@ -3709,6 +3919,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public static synchronized PluginList getPluginList() {
+ checkThread();
return new PluginList();
}
@@ -3717,7 +3928,9 @@ public class WebView extends AbsoluteLayout
* @deprecated This was used for Gears, which has been deprecated.
*/
@Deprecated
- public void refreshPlugins(boolean reloadOpenPages) { }
+ public void refreshPlugins(boolean reloadOpenPages) {
+ checkThread();
+ }
//-------------------------------------------------------------------------
// Override View methods
@@ -3726,7 +3939,7 @@ public class WebView extends AbsoluteLayout
@Override
protected void finalize() throws Throwable {
try {
- destroy();
+ destroyImpl();
} finally {
super.finalize();
}
@@ -4045,6 +4258,10 @@ public class WebView extends AbsoluteLayout
}
}
+ int getBaseLayer() {
+ return nativeGetBaseLayer();
+ }
+
private void onZoomAnimationStart() {
// If it is in password mode, turn it off so it does not draw misplaced.
if (inEditingMode() && nativeFocusCandidateIsPassword()) {
@@ -4068,7 +4285,7 @@ public class WebView extends AbsoluteLayout
}
void onFixedLengthZoomAnimationEnd() {
- if (!mSelectingText) {
+ if (!mBlockWebkitViewMessages && !mSelectingText) {
WebViewCore.resumeUpdatePicture(mWebViewCore);
}
onZoomAnimationEnd();
@@ -4169,7 +4386,7 @@ public class WebView extends AbsoluteLayout
// synchronization problem with layers.
int content = nativeDraw(canvas, color, extras, false);
canvas.setDrawFilter(null);
- if (content != 0) {
+ if (!mBlockWebkitViewMessages && content != 0) {
mWebViewCore.sendMessage(EventHub.SPLIT_PICTURE_SET, content, 0);
}
}
@@ -4225,15 +4442,20 @@ public class WebView extends AbsoluteLayout
}
WebViewCore.CursorData cursorData() {
- WebViewCore.CursorData result = new WebViewCore.CursorData();
- result.mMoveGeneration = nativeMoveGeneration();
- result.mFrame = nativeCursorFramePointer();
+ WebViewCore.CursorData result = cursorDataNoPosition();
Point position = nativeCursorPosition();
result.mX = position.x;
result.mY = position.y;
return result;
}
+ WebViewCore.CursorData cursorDataNoPosition() {
+ WebViewCore.CursorData result = new WebViewCore.CursorData();
+ result.mMoveGeneration = nativeMoveGeneration();
+ result.mFrame = nativeCursorFramePointer();
+ return result;
+ }
+
/**
* Delete text from start to end in the focused textfield. If there is no
* focus, or if start == end, silently fail. If start and end are out of
@@ -4568,6 +4790,9 @@ public class WebView extends AbsoluteLayout
@Override
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ if (mBlockWebkitViewMessages) {
+ return false;
+ }
// send complex characters to webkit for use by JS and plugins
if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
// pass the key to DOM
@@ -4592,6 +4817,9 @@ public class WebView extends AbsoluteLayout
+ "keyCode=" + keyCode
+ ", " + event + ", unicode=" + event.getUnicodeChar());
}
+ if (mBlockWebkitViewMessages) {
+ return false;
+ }
// don't implement accelerator keys here; defer to host application
if (event.isCtrlPressed()) {
@@ -4795,6 +5023,9 @@ public class WebView extends AbsoluteLayout
Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
+ ", " + event + ", unicode=" + event.getUnicodeChar());
}
+ if (mBlockWebkitViewMessages) {
+ return false;
+ }
if (mNativeClass == 0) {
return false;
@@ -4989,6 +5220,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public void emulateShiftHeld() {
+ checkThread();
setUpSelect(false, 0, 0);
}
@@ -5305,6 +5537,13 @@ public class WebView extends AbsoluteLayout
}
mZoomManager.onSizeChanged(w, h, ow, oh);
+
+ if (mLoadedPicture != null && mDelaySetPicture == null) {
+ // Size changes normally result in a new picture
+ // Re-set the loaded picture to simulate that
+ // However, do not update the base layer as that hasn't changed
+ setNewPicture(mLoadedPicture, false);
+ }
}
@Override
@@ -5378,10 +5617,12 @@ public class WebView extends AbsoluteLayout
}
private boolean shouldForwardTouchEvent() {
- return mFullScreenHolder != null || (mForwardTouchEvents
+ if (mFullScreenHolder != null) return true;
+ if (mBlockWebkitViewMessages) return false;
+ return mForwardTouchEvents
&& !mSelectingText
&& mPreventDefault != PREVENT_DEFAULT_IGNORE
- && mPreventDefault != PREVENT_DEFAULT_NO);
+ && mPreventDefault != PREVENT_DEFAULT_NO;
}
private boolean inFullScreenMode() {
@@ -5390,7 +5631,7 @@ public class WebView extends AbsoluteLayout
private void dismissFullScreenMode() {
if (inFullScreenMode()) {
- mFullScreenHolder.dismiss();
+ mFullScreenHolder.hide();
mFullScreenHolder = null;
}
}
@@ -5432,6 +5673,18 @@ public class WebView extends AbsoluteLayout
private static final int DRAG_LAYER_FINGER_DISTANCE = 20000;
@Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (mNativeClass == 0) {
+ return false;
+ }
+ WebViewCore.CursorData data = cursorDataNoPosition();
+ data.mX = viewToContentX((int) event.getX());
+ data.mY = viewToContentY((int) event.getY());
+ mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
+ return true;
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
if (mNativeClass == 0 || (!isClickable() && !isLongClickable())) {
return false;
@@ -5500,25 +5753,31 @@ public class WebView extends AbsoluteLayout
// commit the short press action for the previous tap
doShortPress();
mTouchMode = TOUCH_INIT_MODE;
- mDeferTouchProcess = (!inFullScreenMode()
- && mForwardTouchEvents) ? hitFocusedPlugin(
- contentX, contentY) : false;
+ mDeferTouchProcess = !mBlockWebkitViewMessages
+ && (!inFullScreenMode() && mForwardTouchEvents)
+ ? hitFocusedPlugin(contentX, contentY)
+ : false;
}
} else { // the normal case
mTouchMode = TOUCH_INIT_MODE;
- mDeferTouchProcess = (!inFullScreenMode()
- && mForwardTouchEvents) ? hitFocusedPlugin(
- contentX, contentY) : false;
- mWebViewCore.sendMessage(
- EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
+ mDeferTouchProcess = !mBlockWebkitViewMessages
+ && (!inFullScreenMode() && mForwardTouchEvents)
+ ? hitFocusedPlugin(contentX, contentY)
+ : false;
+ if (!mBlockWebkitViewMessages) {
+ mWebViewCore.sendMessage(
+ EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
+ }
if (getSettings().supportTouchOnly()) {
TouchHighlightData data = new TouchHighlightData();
data.mX = contentX;
data.mY = contentY;
data.mSlop = viewToContentDimension(mNavSlop);
- mWebViewCore.sendMessageDelayed(
- EventHub.GET_TOUCH_HIGHLIGHT_RECTS, data,
- ViewConfiguration.getTapTimeout());
+ if (!mBlockWebkitViewMessages) {
+ mWebViewCore.sendMessageDelayed(
+ EventHub.GET_TOUCH_HIGHLIGHT_RECTS, data,
+ ViewConfiguration.getTapTimeout());
+ }
if (DEBUG_TOUCH_HIGHLIGHT) {
if (getSettings().getNavDump()) {
mTouchHighlightX = (int) x + mScrollX;
@@ -5554,7 +5813,7 @@ public class WebView extends AbsoluteLayout
SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
if (inFullScreenMode() || mDeferTouchProcess) {
mPreventDefault = PREVENT_DEFAULT_YES;
- } else if (mForwardTouchEvents) {
+ } else if (!mBlockWebkitViewMessages && mForwardTouchEvents) {
mPreventDefault = PREVENT_DEFAULT_MAYBE_YES;
} else {
mPreventDefault = PREVENT_DEFAULT_NO;
@@ -6257,7 +6516,11 @@ public class WebView extends AbsoluteLayout
// arrow key events, not trackball events, from one child to the next
private boolean mMapTrackballToArrowKeys = true;
+ private DrawData mDelaySetPicture;
+ private DrawData mLoadedPicture;
+
public void setMapTrackballToArrowKeys(boolean setMap) {
+ checkThread();
mMapTrackballToArrowKeys = setMap;
}
@@ -6550,6 +6813,7 @@ public class WebView extends AbsoluteLayout
}
public void flingScroll(int vx, int vy) {
+ checkThread();
mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
invalidate();
@@ -6588,11 +6852,6 @@ public class WebView extends AbsoluteLayout
vx = 0;
}
}
- if (true /* EMG release: make our fling more like Maps' */) {
- // maps cuts their velocity in half
- vx = vx * 3 / 4;
- vy = vy * 3 / 4;
- }
if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
WebViewCore.resumePriority();
if (!mSelectingText) {
@@ -6685,6 +6944,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public View getZoomControls() {
+ checkThread();
if (!getSettings().supportZoom()) {
Log.w(LOGTAG, "This WebView doesn't support zoom.");
return null;
@@ -6704,6 +6964,7 @@ public class WebView extends AbsoluteLayout
* @return TRUE if the WebView can be zoomed in.
*/
public boolean canZoomIn() {
+ checkThread();
return mZoomManager.canZoomIn();
}
@@ -6711,6 +6972,7 @@ public class WebView extends AbsoluteLayout
* @return TRUE if the WebView can be zoomed out.
*/
public boolean canZoomOut() {
+ checkThread();
return mZoomManager.canZoomOut();
}
@@ -6719,6 +6981,7 @@ public class WebView extends AbsoluteLayout
* @return TRUE if zoom in succeeds. FALSE if no zoom changes.
*/
public boolean zoomIn() {
+ checkThread();
return mZoomManager.zoomIn();
}
@@ -6727,6 +6990,7 @@ public class WebView extends AbsoluteLayout
* @return TRUE if zoom out succeeds. FALSE if no zoom changes.
*/
public boolean zoomOut() {
+ checkThread();
return mZoomManager.zoomOut();
}
@@ -7027,7 +7291,6 @@ public class WebView extends AbsoluteLayout
if (measuredHeight > heightSize) {
measuredHeight = heightSize;
mHeightCanMeasure = false;
- } else if (measuredHeight < heightSize) {
measuredHeight |= MEASURED_STATE_TOO_SMALL;
}
}
@@ -7144,7 +7407,10 @@ public class WebView extends AbsoluteLayout
cursorData(), 1000);
}
- /* package */ synchronized WebViewCore getWebViewCore() {
+ /**
+ * @hide
+ */
+ public synchronized WebViewCore getWebViewCore() {
return mWebViewCore;
}
@@ -7656,6 +7922,11 @@ public class WebView extends AbsoluteLayout
// after WebView's destroy() is called, skip handling messages.
return;
}
+ if (mBlockWebkitViewMessages
+ && msg.what != WEBCORE_INITIALIZED_MSG_ID) {
+ // Blocking messages from webkit
+ return;
+ }
switch (msg.what) {
case REMEMBER_PASSWORD: {
mDatabase.setUsernamePassword(
@@ -7791,71 +8062,19 @@ public class WebView extends AbsoluteLayout
case NEW_PICTURE_MSG_ID: {
// called for new content
final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
- WebViewCore.ViewState viewState = draw.mViewState;
- boolean isPictureAfterFirstLayout = viewState != null;
- setBaseLayer(draw.mBaseLayer, draw.mInvalRegion,
- getSettings().getShowVisualIndicator(),
- isPictureAfterFirstLayout);
- final Point viewSize = draw.mViewSize;
- if (isPictureAfterFirstLayout) {
- // Reset the last sent data here since dealing with new page.
- mLastWidthSent = 0;
- mZoomManager.onFirstLayout(draw);
- if (!mDrawHistory) {
- // Do not send the scroll event for this particular
- // scroll message. Note that a scroll event may
- // still be fired if the user scrolls before the
- // message can be handled.
- mSendScrollEvent = false;
- setContentScrollTo(viewState.mScrollX, viewState.mScrollY);
- mSendScrollEvent = true;
-
- // As we are on a new page, remove the WebTextView. This
- // is necessary for page loads driven by webkit, and in
- // particular when the user was on a password field, so
- // the WebTextView was visible.
- clearTextEntry();
- }
- }
-
- // We update the layout (i.e. request a layout from the
- // view system) if the last view size that we sent to
- // WebCore matches the view size of the picture we just
- // received in the fixed dimension.
- final boolean updateLayout = viewSize.x == mLastWidthSent
- && viewSize.y == mLastHeightSent;
- // Don't send scroll event for picture coming from webkit,
- // since the new picture may cause a scroll event to override
- // the saved history scroll position.
- mSendScrollEvent = false;
- recordNewContentSize(draw.mContentSize.x,
- draw.mContentSize.y, updateLayout);
- mSendScrollEvent = true;
- if (DebugFlags.WEB_VIEW) {
- Rect b = draw.mInvalRegion.getBounds();
- Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
- b.left+","+b.top+","+b.right+","+b.bottom+"}");
- }
- invalidateContentRect(draw.mInvalRegion.getBounds());
-
- if (mPictureListener != null) {
- mPictureListener.onNewPicture(WebView.this, capturePicture());
- }
-
- // update the zoom information based on the new picture
- mZoomManager.onNewPicture(draw);
-
- if (draw.mFocusSizeChanged && inEditingMode()) {
- mFocusSizeChanged = true;
- }
- if (isPictureAfterFirstLayout) {
- mViewManager.postReadyToDrawAll();
- }
+ setNewPicture(draw, true);
break;
}
case WEBCORE_INITIALIZED_MSG_ID:
// nativeCreate sets mNativeClass to a non-zero value
- nativeCreate(msg.arg1);
+ String drawableDir = BrowserFrame.getRawResFilename(
+ BrowserFrame.DRAWABLEDIR, mContext);
+ AssetManager am = mContext.getAssets();
+ nativeCreate(msg.arg1, drawableDir, am);
+ if (mDelaySetPicture != null) {
+ setNewPicture(mDelaySetPicture, true);
+ mDelaySetPicture = null;
+ }
break;
case UPDATE_TEXTFIELD_TEXT_MSG_ID:
// Make sure that the textfield is currently focused
@@ -8020,16 +8239,15 @@ public class WebView extends AbsoluteLayout
case SHOW_FULLSCREEN: {
View view = (View) msg.obj;
- int npp = msg.arg1;
+ int orientation = msg.arg1;
+ int npp = msg.arg2;
if (inFullScreenMode()) {
Log.w(LOGTAG, "Should not have another full screen.");
dismissFullScreenMode();
}
- mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp);
+ mFullScreenHolder = new PluginFullScreenHolder(WebView.this, orientation, npp);
mFullScreenHolder.setContentView(view);
- mFullScreenHolder.setCancelable(false);
- mFullScreenHolder.setCanceledOnTouchOutside(false);
mFullScreenHolder.show();
break;
@@ -8167,6 +8385,80 @@ public class WebView extends AbsoluteLayout
}
}
+ void setNewPicture(final WebViewCore.DrawData draw, boolean updateBaseLayer) {
+ if (mNativeClass == 0) {
+ if (mDelaySetPicture != null) {
+ throw new IllegalStateException("Tried to setNewPicture with"
+ + " a delay picture already set! (memory leak)");
+ }
+ // Not initialized yet, delay set
+ mDelaySetPicture = draw;
+ return;
+ }
+ WebViewCore.ViewState viewState = draw.mViewState;
+ boolean isPictureAfterFirstLayout = viewState != null;
+ if (updateBaseLayer) {
+ setBaseLayer(draw.mBaseLayer, draw.mInvalRegion,
+ getSettings().getShowVisualIndicator(),
+ isPictureAfterFirstLayout);
+ }
+ final Point viewSize = draw.mViewSize;
+ if (isPictureAfterFirstLayout) {
+ // Reset the last sent data here since dealing with new page.
+ mLastWidthSent = 0;
+ mZoomManager.onFirstLayout(draw);
+ if (!mDrawHistory) {
+ // Do not send the scroll event for this particular
+ // scroll message. Note that a scroll event may
+ // still be fired if the user scrolls before the
+ // message can be handled.
+ mSendScrollEvent = false;
+ setContentScrollTo(viewState.mScrollX, viewState.mScrollY);
+ mSendScrollEvent = true;
+
+ // As we are on a new page, remove the WebTextView. This
+ // is necessary for page loads driven by webkit, and in
+ // particular when the user was on a password field, so
+ // the WebTextView was visible.
+ clearTextEntry();
+ }
+ }
+
+ // We update the layout (i.e. request a layout from the
+ // view system) if the last view size that we sent to
+ // WebCore matches the view size of the picture we just
+ // received in the fixed dimension.
+ final boolean updateLayout = viewSize.x == mLastWidthSent
+ && viewSize.y == mLastHeightSent;
+ // Don't send scroll event for picture coming from webkit,
+ // since the new picture may cause a scroll event to override
+ // the saved history scroll position.
+ mSendScrollEvent = false;
+ recordNewContentSize(draw.mContentSize.x,
+ draw.mContentSize.y, updateLayout);
+ mSendScrollEvent = true;
+ if (DebugFlags.WEB_VIEW) {
+ Rect b = draw.mInvalRegion.getBounds();
+ Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
+ b.left+","+b.top+","+b.right+","+b.bottom+"}");
+ }
+ invalidateContentRect(draw.mInvalRegion.getBounds());
+
+ if (mPictureListener != null) {
+ mPictureListener.onNewPicture(WebView.this, capturePicture());
+ }
+
+ // update the zoom information based on the new picture
+ mZoomManager.onNewPicture(draw);
+
+ if (draw.mFocusSizeChanged && inEditingMode()) {
+ mFocusSizeChanged = true;
+ }
+ if (isPictureAfterFirstLayout) {
+ mViewManager.postReadyToDrawAll();
+ }
+ }
+
/**
* Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID
* and UPDATE_TEXT_SELECTION_MSG_ID. Update the selection of WebTextView.
@@ -8684,6 +8976,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public void debugDump() {
+ checkThread();
nativeDebugDump();
mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
}
@@ -8745,12 +9038,24 @@ public class WebView extends AbsoluteLayout
return mViewManager;
}
+ private static void checkThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ RuntimeException exception = new RuntimeException(
+ "A WebView method was called on thread '" +
+ Thread.currentThread().getName() + "'. " +
+ "All WebView methods must be called on the UI thread. " +
+ "Future versions of WebView may not support use on other threads.");
+ Log.e(LOGTAG, Log.getStackTraceString(exception));
+ StrictMode.onWebViewMethodCalledOnWrongThread(exception);
+ }
+ }
+
private native int nativeCacheHitFramePointer();
private native boolean nativeCacheHitIsPlugin();
private native Rect nativeCacheHitNodeBounds();
private native int nativeCacheHitNodePointer();
/* package */ native void nativeClearCursor();
- private native void nativeCreate(int ptr);
+ private native void nativeCreate(int ptr, String drawableDir, AssetManager am);
private native int nativeCursorFramePointer();
private native Rect nativeCursorNodeBounds();
private native int nativeCursorNodePointer();
@@ -8856,6 +9161,7 @@ public class WebView extends AbsoluteLayout
private native void nativeSetHeightCanMeasure(boolean measure);
private native void nativeSetBaseLayer(int layer, Region invalRegion,
boolean showVisualIndicator, boolean isPictureAfterFirstLayout);
+ private native int nativeGetBaseLayer();
private native void nativeShowCursorTimed();
private native void nativeReplaceBaseContent(int content);
private native void nativeCopyBaseContentToPicture(Picture pict);
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 65026a5..d3be2bf 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -30,7 +30,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.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return True if the host application wants to leave the current WebView
@@ -46,7 +46,7 @@ public class WebViewClient {
* framesets will call onPageStarted one time for the main frame. This also
* means that onPageStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @param favicon The favicon for this page if it already exists in the
@@ -60,7 +60,7 @@ public class WebViewClient {
* is called only for main frame. When onPageFinished() is called, the
* rendering picture may not be updated yet. To get the notification for the
* new Picture, use {@link WebView.PictureListener#onNewPicture}.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param url The url of the page.
*/
@@ -70,7 +70,7 @@ public class WebViewClient {
/**
* Notify the host application that the WebView will load the resource
* specified by the given url.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param url The url of the resource the WebView will load.
*/
@@ -102,7 +102,7 @@ public class WebViewClient {
* HTTP redirects. As the host application if it would like to continue
* trying to load the resource. The default behavior is to send the cancel
* message.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param cancelMsg The message to send if the host wants to cancel
* @param continueMsg The message to send if the host wants to continue
@@ -164,7 +164,7 @@ public class WebViewClient {
* As the host application if the browser should resend data as the
* requested page was a result of a POST. The default is to not resend the
* data.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param dontResend The message to send if the browser should not resend
* @param resend The message to send if the browser should resend data
@@ -176,7 +176,7 @@ public class WebViewClient {
/**
* Notify the host application to update its visited links database.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param url The url being visited.
* @param isReload True if this url is being reloaded.
@@ -186,12 +186,12 @@ public class WebViewClient {
}
/**
- * Notify the host application to handle a ssl certificate error request
+ * Notify the host application to handle a SSL certificate error request
* (display the error to the user and ask whether to proceed or not). The
* host application has to call either handler.cancel() or handler.proceed()
* as the connection is suspended and waiting for the response. The default
* behavior is to cancel the load.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's
* response.
@@ -203,9 +203,29 @@ public class WebViewClient {
}
/**
+ * Notify the host application to handle a SSL client certificate
+ * request (display the request to the user and ask whether to
+ * proceed with a client certificate or not). The host application
+ * has to call either handler.cancel() or handler.proceed() as the
+ * connection is suspended and waiting for the response. The
+ * default behavior is to cancel, returning no client certificate.
+ *
+ * @param view The WebView that is initiating the callback.
+ * @param handler An ClientCertRequestHandler object that will
+ * handle the user's response.
+ * @param host_and_port The host and port of the requesting server.
+ *
+ * @hide
+ */
+ public void onReceivedClientCertRequest(WebView view,
+ ClientCertRequestHandler handler, String host_and_port) {
+ handler.cancel();
+ }
+
+ /**
* Notify the host application to handle an authentication request. The
* default behavior is to cancel the request.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param handler The HttpAuthHandler that will handle the user's response.
* @param host The host requiring authentication.
@@ -223,7 +243,7 @@ public class WebViewClient {
* true, WebView will not handle the key event. If return false, WebView
* will always handle the key event, so none of the super in the view chain
* will see the key event. The default behavior returns false.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param event The key event.
* @return True if the host application wants to handle the key event
@@ -239,7 +259,7 @@ public class WebViewClient {
* or if shouldOverrideKeyEvent returns true. This is called asynchronously
* from where the key is dispatched. It gives the host application an chance
* to handle the unhandled key events.
- *
+ *
* @param view The WebView that is initiating the callback.
* @param event The key event.
*/
@@ -249,7 +269,7 @@ public class WebViewClient {
/**
* Notify the host application that the scale applied to the WebView has
* changed.
- *
+ *
* @param view he WebView that is initiating the callback.
* @param oldScale The old scale factor
* @param newScale The new scale factor
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index e73c9d0..06a61bd 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -49,7 +49,10 @@ import java.util.Set;
import junit.framework.Assert;
-final class WebViewCore {
+/**
+ * @hide
+ */
+public final class WebViewCore {
private static final String LOGTAG = "webcore";
@@ -906,7 +909,10 @@ final class WebViewCore {
"REMOVE_JS_INTERFACE", // = 149;
};
- class EventHub {
+ /**
+ * @hide
+ */
+ public class EventHub {
// Message Ids
static final int REVEAL_SELECTION = 96;
static final int REQUEST_LABEL = 97;
@@ -937,7 +943,7 @@ final class WebViewCore {
static final int DELETE_SELECTION = 122;
static final int LISTBOX_CHOICES = 123;
static final int SINGLE_LISTBOX_CHOICE = 124;
- static final int MESSAGE_RELAY = 125;
+ public static final int MESSAGE_RELAY = 125;
static final int SET_BACKGROUND_COLOR = 126;
static final int SET_MOVE_FOCUS = 127;
static final int SAVE_DOCUMENT_STATE = 128;
@@ -1135,16 +1141,13 @@ final class WebViewCore {
if (baseUrl != null) {
int i = baseUrl.indexOf(':');
if (i > 0) {
- /*
- * In 1.0, {@link
- * WebView#loadDataWithBaseURL} can access
- * local asset files as long as the data is
- * valid. In the new WebKit, the restriction
- * is tightened. To be compatible with 1.0,
- * we automatically add the scheme of the
- * baseUrl for local access as long as it is
- * not http(s)/ftp(s)/about/javascript
- */
+ // In 1.0, WebView.loadDataWithBaseURL() could access local
+ // asset files using 'file' scheme URLs as long as the data is
+ // valid. Later versions of WebKit have tightened the
+ // restriction around when pages can access such local URLs.
+ // To maintain compatibility with 1.0, we register the scheme of
+ // the baseUrl to be considered local, as long as it is not
+ // http(s)/ftp(s)/about/javascript.
String scheme = baseUrl.substring(0, i);
if (!scheme.startsWith("http") &&
!scheme.startsWith("ftp") &&
@@ -1703,7 +1706,10 @@ final class WebViewCore {
// If it needs WebCore, it has to send message.
//-------------------------------------------------------------------------
- void sendMessage(Message msg) {
+ /**
+ * @hide
+ */
+ public void sendMessage(Message msg) {
mEventHub.sendMessage(msg);
}
@@ -1857,7 +1863,7 @@ final class WebViewCore {
Log.w(LOGTAG, "skip viewSizeChanged as w is 0");
return;
}
- int width = calculateWindowWidth(w, textwrapWidth);
+ int width = calculateWindowWidth(w);
int height = h;
if (width != w) {
float heightWidthRatio = data.mHeightWidthRatio;
@@ -1883,41 +1889,18 @@ final class WebViewCore {
}
// Calculate width to be used in webkit window.
- private int calculateWindowWidth(int viewWidth, int textwrapWidth) {
+ private int calculateWindowWidth(int viewWidth) {
int width = viewWidth;
if (mSettings.getUseWideViewPort()) {
if (mViewportWidth == -1) {
- if (mSettings.getLayoutAlgorithm() ==
- WebSettings.LayoutAlgorithm.NORMAL || mSettings.getUseFixedViewport()) {
- width = WebView.DEFAULT_VIEWPORT_WIDTH;
- } else {
- /*
- * if a page's minimum preferred width is wider than the
- * given "w", use it instead to get better layout result. If
- * we start a page with MAX_ZOOM_WIDTH, "w" will be always
- * wider. If we start a page with screen width, due to the
- * delay between {@link #didFirstLayout} and
- * {@link #viewSizeChanged},
- * {@link #nativeGetContentMinPrefWidth} will return a more
- * accurate value than initial 0 to result a better layout.
- * In the worse case, the native width will be adjusted when
- * next zoom or screen orientation change happens.
- */
- width = Math.min(WebView.sMaxViewportWidth, Math.max(viewWidth,
- Math.max(WebView.DEFAULT_VIEWPORT_WIDTH,
- nativeGetContentMinPrefWidth())));
- }
+ // Fixed viewport width.
+ width = WebView.DEFAULT_VIEWPORT_WIDTH;
} else if (mViewportWidth > 0) {
- if (mSettings.getUseFixedViewport()) {
- // Use website specified or desired fixed viewport width.
- width = mViewportWidth;
- } else {
- width = Math.max(viewWidth, mViewportWidth);
- }
- } else if (mSettings.getUseFixedViewport()) {
- width = mWebView.getViewWidth();
+ // Use website specified or desired fixed viewport width.
+ width = mViewportWidth;
} else {
- width = textwrapWidth;
+ // For mobile web site.
+ width = Math.round(mWebView.getViewWidth() / mWebView.getDefaultZoomScale());
}
}
return width;
@@ -2255,6 +2238,27 @@ final class WebViewCore {
// set the viewport settings from WebKit
setViewportSettingsFromNative();
+ if (mSettings.forceUserScalable()) {
+ mViewportUserScalable = true;
+ if (mViewportInitialScale > 0) {
+ if (mViewportMinimumScale > 0) {
+ mViewportMinimumScale = Math.min(mViewportMinimumScale,
+ mViewportInitialScale / 2);
+ }
+ if (mViewportMaximumScale > 0) {
+ mViewportMaximumScale = Math.max(mViewportMaximumScale,
+ mViewportInitialScale * 2);
+ }
+ } else {
+ if (mViewportMinimumScale > 0) {
+ mViewportMinimumScale = Math.min(mViewportMinimumScale, 50);
+ }
+ if (mViewportMaximumScale > 0) {
+ mViewportMaximumScale = Math.max(mViewportMaximumScale, 200);
+ }
+ }
+ }
+
// adjust the default scale to match the densityDpi
float adjust = 1.0f;
if (mViewportDensityDpi == -1) {
@@ -2411,8 +2415,7 @@ final class WebViewCore {
// in zoom overview mode.
tentativeScale = mInitialViewState.mTextWrapScale;
int tentativeViewWidth = Math.round(webViewWidth / tentativeScale);
- int windowWidth = calculateWindowWidth(tentativeViewWidth,
- tentativeViewWidth);
+ int windowWidth = calculateWindowWidth(tentativeViewWidth);
// In viewport setup time, since no content width is known, we assume
// the windowWidth will be the content width, to get a more likely
// zoom overview scale.
@@ -2421,8 +2424,7 @@ final class WebViewCore {
// If user choose non-overview mode.
data.mScale = Math.max(data.mScale, tentativeScale);
}
- if (mSettings.isNarrowColumnLayout() &&
- mSettings.getUseFixedViewport()) {
+ if (mSettings.isNarrowColumnLayout()) {
// In case of automatic text reflow in fixed view port mode.
mInitialViewState.mTextWrapScale =
ZoomManager.computeReadingLevelScale(data.mScale);
@@ -2591,11 +2593,11 @@ final class WebViewCore {
// called by JNI
private Class<?> getPluginClass(String libName, String clsName) {
-
+
if (mWebView == null) {
return null;
}
-
+
PluginManager pluginManager = PluginManager.getInstance(null);
String pkgName = pluginManager.getPluginsAPKName(libName);
@@ -2603,7 +2605,7 @@ final class WebViewCore {
Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
return null;
}
-
+
try {
return pluginManager.getPluginClass(pkgName, clsName);
} catch (NameNotFoundException e) {
@@ -2618,14 +2620,15 @@ final class WebViewCore {
// called by JNI. PluginWidget function to launch a full-screen view using a
// View object provided by the plugin class.
- private void showFullScreenPlugin(ViewManager.ChildView childView, int npp) {
+ private void showFullScreenPlugin(ViewManager.ChildView childView, int orientation, int npp) {
if (mWebView == null) {
return;
}
Message message = mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN);
message.obj = childView.mView;
- message.arg1 = npp;
+ message.arg1 = orientation;
+ message.arg2 = npp;
message.sendToTarget();
}
@@ -2658,7 +2661,7 @@ final class WebViewCore {
view.mView = pluginView;
return view;
}
-
+
// called by JNI. PluginWidget functions for creating an embedded View for
// the surface drawing model.
private ViewManager.ChildView addSurface(View pluginView, int x, int y,
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index f2a1ec3..fe6fb2f 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -634,8 +634,17 @@ class ZoomManager {
} else {
newTextWrapScale = mActualScale;
}
+ final boolean firstTimeReflow = !exceedsMinScaleIncrement(mActualScale, mTextWrapScale);
+ if (firstTimeReflow || mInZoomOverview) {
+ // In case first time reflow or in zoom overview mode, let reflow and zoom
+ // happen at the same time.
+ mTextWrapScale = newTextWrapScale;
+ }
if (settings.isNarrowColumnLayout()
- && exceedsMinScaleIncrement(mTextWrapScale, newTextWrapScale)) {
+ && exceedsMinScaleIncrement(mTextWrapScale, newTextWrapScale)
+ && !firstTimeReflow
+ && !mInZoomOverview) {
+ // Reflow only.
mTextWrapScale = newTextWrapScale;
refreshZoomScale(true);
} else if (!mInZoomOverview && willScaleTriggerZoom(getZoomOverviewScale())) {
@@ -906,7 +915,7 @@ class ZoomManager {
// scaleAll(), we need to post a Runnable to ensure requestLayout().
// Additionally, only update the text wrap scale if the width changed.
mWebView.post(new PostScale(w != ow &&
- !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview));
+ !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview, w < ow));
}
private class PostScale implements Runnable {
@@ -915,10 +924,14 @@ class ZoomManager {
// it could be changed between the time this callback is initiated and
// the time it's actually run.
final boolean mInZoomOverviewBeforeSizeChange;
+ final boolean mInPortraitMode;
- public PostScale(boolean updateTextWrap, boolean inZoomOverview) {
+ public PostScale(boolean updateTextWrap,
+ boolean inZoomOverview,
+ boolean inPortraitMode) {
mUpdateTextWrap = updateTextWrap;
mInZoomOverviewBeforeSizeChange = inZoomOverview;
+ mInPortraitMode = inPortraitMode;
}
public void run() {
@@ -926,9 +939,12 @@ class ZoomManager {
// we always force, in case our height changed, in which case we
// still want to send the notification over to webkit.
// Keep overview mode unchanged when rotating.
- final float zoomOverviewScale = getZoomOverviewScale();
- final float newScale = (mInZoomOverviewBeforeSizeChange) ?
- zoomOverviewScale : Math.max(mActualScale, zoomOverviewScale);
+ float newScale = mActualScale;
+ if (mWebView.getSettings().getUseWideViewPort() &&
+ mInPortraitMode &&
+ mInZoomOverviewBeforeSizeChange) {
+ newScale = getZoomOverviewScale();
+ }
setZoomScale(newScale, mUpdateTextWrap, true);
// update the zoom buttons as the scale can be changed
updateZoomPicker();
@@ -999,8 +1015,9 @@ class ZoomManager {
boolean mobileSiteInOverview = mInZoomOverview &&
!exceedsMinScaleIncrement(newZoomOverviewScale, 1.0f);
if (!mWebView.drawHistory() &&
- (mInitialZoomOverview || scaleLessThanOverview || mobileSiteInOverview) &&
- scaleHasDiff && zoomOverviewWidthChanged) {
+ (scaleLessThanOverview ||
+ ((mInitialZoomOverview || mobileSiteInOverview) &&
+ scaleHasDiff && zoomOverviewWidthChanged))) {
mInitialZoomOverview = false;
setZoomScale(newZoomOverviewScale, !willScaleTriggerZoom(mTextWrapScale) &&
!mWebView.getSettings().getUseFixedViewport());
@@ -1019,23 +1036,15 @@ class ZoomManager {
WebSettings settings = mWebView.getSettings();
int newZoomOverviewWidth = mZoomOverviewWidth;
if (settings.getUseWideViewPort()) {
- if (!settings.getUseFixedViewport()) {
- // limit mZoomOverviewWidth upper bound to
- // sMaxViewportWidth so that if the page doesn't behave
- // well, the WebView won't go insane. limit the lower
- // bound to match the default scale for mobile sites.
- newZoomOverviewWidth = Math.min(WebView.sMaxViewportWidth,
- Math.max((int) (viewWidth * mInvDefaultScale),
- Math.max(drawData.mMinPrefWidth, drawData.mViewSize.x)));
- } else if (drawData.mContentSize.x > 0) {
+ if (drawData.mContentSize.x > 0) {
// The webkitDraw for layers will not populate contentSize, and it'll be
// ignored for zoom overview width update.
- final int contentWidth = Math.max(drawData.mContentSize.x, drawData.mMinPrefWidth);
- newZoomOverviewWidth = Math.min(WebView.sMaxViewportWidth, contentWidth);
+ newZoomOverviewWidth = Math.min(WebView.sMaxViewportWidth,
+ drawData.mContentSize.x);
}
} else {
// If not use wide viewport, use view width as the zoom overview width.
- newZoomOverviewWidth = viewWidth;
+ newZoomOverviewWidth = Math.round(viewWidth / mDefaultScale);
}
if (newZoomOverviewWidth != mZoomOverviewWidth) {
setZoomOverviewWidth(newZoomOverviewWidth);
diff --git a/core/java/android/webkit/webdriver/By.java b/core/java/android/webkit/webdriver/By.java
new file mode 100644
index 0000000..fa4fe74
--- /dev/null
+++ b/core/java/android/webkit/webdriver/By.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import java.util.List;
+
+/**
+ * Mechanism to locate elements within the DOM of the page.
+ * @hide
+ */
+public abstract class By {
+ public abstract WebElement findElement(WebElement element);
+ public abstract List<WebElement> findElements(WebElement element);
+
+ /**
+ * Locates an element by its HTML id attribute.
+ *
+ * @param id The HTML id attribute to look for.
+ * @return A By instance that locates elements by their HTML id attributes.
+ */
+ public static By id(final String id) {
+ throwIfNull(id);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementById(id);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsById(id); // Yes, it happens a lot.
+ }
+
+ @Override
+ public String toString() {
+ return "By.id: " + id;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by the matching the exact text on the HTML link.
+ *
+ * @param linkText The exact text to match against.
+ * @return A By instance that locates elements by the text displayed by
+ * the link.
+ */
+ public static By linkText(final String linkText) {
+ throwIfNull(linkText);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByLinkText(linkText);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByLinkText(linkText);
+ }
+
+ @Override
+ public String toString() {
+ return "By.linkText: " + linkText;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching partial part of the text displayed by an
+ * HTML link.
+ *
+ * @param linkText The text that should be contained by the text displayed
+ * on the link.
+ * @return A By instance that locates elements that contain the given link
+ * text.
+ */
+ public static By partialLinkText(final String linkText) {
+ throwIfNull(linkText);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByPartialLinkText(linkText);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByPartialLinkText(linkText);
+ }
+
+ @Override
+ public String toString() {
+ return "By.partialLinkText: " + linkText;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its HTML name attribute.
+ *
+ * @param name The value of the HTML name attribute.
+ * @return A By instance that locates elements by the HTML name attribute.
+ */
+ public static By name(final String name) {
+ throwIfNull(name);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByName(name);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByName(name);
+ }
+
+ @Override
+ public String toString() {
+ return "By.name: " + name;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its class name.
+ * @param className The class name
+ * @return A By instance that locates elements by their class name attribute.
+ */
+ public static By className(final String className) {
+ throwIfNull(className);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByClassName(className);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByClassName(className);
+ }
+
+ @Override
+ public String toString() {
+ return "By.className: " + className;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its css property.
+ *
+ * @param css The css property.
+ * @return A By instance that locates elements by their css property.
+ */
+ public static By css(final String css) {
+ throwIfNull(css);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByCss(css);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByCss(css);
+ }
+
+ @Override
+ public String toString() {
+ return "By.css: " + css;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its HTML tag name.
+ *
+ * @param tagName The HTML tag name to look for.
+ * @return A By instance that locates elements using the name of the
+ * HTML tag.
+ */
+ public static By tagName(final String tagName) {
+ throwIfNull(tagName);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByTagName(tagName);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByTagName(tagName);
+ }
+
+ @Override
+ public String toString() {
+ return "By.tagName: " + tagName;
+ }
+ };
+ }
+
+ /**
+ * Locates an element using an XPath expression.
+ *
+ * <p>When using XPath, be aware that this follows standard conventions: a
+ * search prefixed with "//" will search the entire document, not just the
+ * children of the current node. Use ".//" to limit your search to the
+ * children of this {@link android.webkit.webdriver.WebElement}.
+ *
+ * @param xpath The XPath expression to use.
+ * @return A By instance that locates elements using the given XPath.
+ */
+ public static By xpath(final String xpath) {
+ throwIfNull(xpath);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByXPath(xpath);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByXPath(xpath);
+ }
+
+ @Override
+ public String toString() {
+ return "By.xpath: " + xpath;
+ }
+ };
+ }
+
+ private static void throwIfNull(String argument) {
+ if (argument == null) {
+ throw new IllegalArgumentException(
+ "Cannot find elements with null locator.");
+ }
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebDriver.java b/core/java/android/webkit/webdriver/WebDriver.java
new file mode 100644
index 0000000..79e6523
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebDriver.java
@@ -0,0 +1,843 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.webkit.WebView;
+import android.webkit.WebViewCore;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import com.android.internal.R;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Drives a web application by controlling the WebView. This class
+ * provides a DOM-like API allowing to get information about the page,
+ * navigate, and interact with the web application. This is particularly useful
+ * for testing a web application.
+ *
+ * <p/>{@link android.webkit.webdriver.WebDriver} should be created in the main
+ * thread, and invoked from another thread. Here is a sample usage:
+ *
+ * public class WebDriverStubActivity extends Activity {
+ * private WebDriver mDriver;
+ *
+ * public void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * WebView view = new WebView(this);
+ * mDriver = new WebDriver(view);
+ * setContentView(view);
+ * }
+ *
+ *
+ * public WebDriver getDriver() {
+ * return mDriver;
+ * }
+ *}
+ *
+ * public class WebDriverTest extends
+ * ActivityInstrumentationTestCase2<WebDriverStubActivity>{
+ * private WebDriver mDriver;
+ *
+ * public WebDriverTest() {
+ * super(WebDriverStubActivity.class);
+ * }
+ *
+ * protected void setUp() throws Exception {
+ * super.setUp();
+ * mDriver = getActivity().getDriver();
+ * }
+ *
+ * public void testGoogle() {
+ * mDriver.get("http://google.com");
+ * WebElement searchBox = mDriver.findElement(By.name("q"));
+ * q.sendKeys("Cheese!");
+ * q.submit();
+ * assertTrue(mDriver.findElements(By.partialLinkText("Cheese")).size() > 0);
+ * }
+ *}
+ *
+ * @hide
+ */
+public class WebDriver {
+ // Timeout for page load in milliseconds.
+ private static final int LOADING_TIMEOUT = 30000;
+ // Timeout for executing JavaScript in the WebView in milliseconds.
+ private static final int JS_EXECUTION_TIMEOUT = 10000;
+ // Timeout for the MotionEvent to be completely handled
+ private static final int MOTION_EVENT_TIMEOUT = 1000;
+ // Timeout for detecting a new page load
+ private static final int PAGE_STARTED_LOADING = 500;
+ // Timeout for handling KeyEvents
+ private static final int KEY_EVENT_TIMEOUT = 2000;
+
+ // Commands posted to the handler
+ private static final int CMD_GET_URL = 1;
+ private static final int CMD_EXECUTE_SCRIPT = 2;
+ private static final int CMD_SEND_TOUCH = 3;
+ private static final int CMD_SEND_KEYS = 4;
+ private static final int CMD_NAV_REFRESH = 5;
+ private static final int CMD_NAV_BACK = 6;
+ private static final int CMD_NAV_FORWARD = 7;
+ private static final int CMD_SEND_KEYCODE = 8;
+ private static final int CMD_MOVE_CURSOR_RIGHTMOST_POS = 9;
+ private static final int CMD_MESSAGE_RELAY_ECHO = 10;
+
+ private static final String ELEMENT_KEY = "ELEMENT";
+ private static final String STATUS = "status";
+ private static final String VALUE = "value";
+
+ private static final long MAIN_THREAD = Thread.currentThread().getId();
+
+ // This is updated by a callabck from JavaScript when the result is ready.
+ private String mJsResult;
+
+ // Used for synchronization
+ private final Object mSyncObject;
+ private final Object mSyncPageLoad;
+
+ // Updated when the command is done executing in the main thread.
+ private volatile boolean mCommandDone;
+ // Used by WebViewClientWrapper.onPageStarted() to notify that
+ // a page started loading.
+ private volatile boolean mPageStartedLoading;
+ // Used by WebChromeClientWrapper.onProgressChanged to notify when
+ // a page finished loading.
+ private volatile boolean mPageFinishedLoading;
+ private WebView mWebView;
+ private Navigation mNavigation;
+ // This WebElement represents the object document.documentElement
+ private WebElement mDocumentElement;
+
+
+ // This Handler runs in the main UI thread.
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_GET_URL:
+ final String url = (String) msg.obj;
+ mWebView.loadUrl(url);
+ break;
+ case CMD_EXECUTE_SCRIPT:
+ mWebView.loadUrl("javascript:" + (String) msg.obj);
+ break;
+ case CMD_MESSAGE_RELAY_ECHO:
+ notifyCommandDone();
+ break;
+ case CMD_SEND_TOUCH:
+ touchScreen((Point) msg.obj);
+ notifyCommandDone();
+ break;
+ case CMD_SEND_KEYS:
+ dispatchKeys((CharSequence[]) msg.obj);
+ notifyCommandDone();
+ break;
+ case CMD_NAV_REFRESH:
+ mWebView.reload();
+ break;
+ case CMD_NAV_BACK:
+ mWebView.goBack();
+ break;
+ case CMD_NAV_FORWARD:
+ mWebView.goForward();
+ break;
+ case CMD_SEND_KEYCODE:
+ dispatchKeyCodes((int[]) msg.obj);
+ notifyCommandDone();
+ break;
+ case CMD_MOVE_CURSOR_RIGHTMOST_POS:
+ moveCursorToLeftMostPos((String) msg.obj);
+ notifyCommandDone();
+ break;
+ }
+ }
+ };
+
+ /**
+ * Error codes from the WebDriver wire protocol
+ * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
+ */
+ private enum ErrorCode {
+ SUCCESS(0),
+ NO_SUCH_ELEMENT(7),
+ NO_SUCH_FRAME(8),
+ UNKNOWN_COMMAND(9),
+ UNSUPPORTED_OPERATION(9), // Alias
+ STALE_ELEMENT_REFERENCE(10),
+ ELEMENT_NOT_VISISBLE(11),
+ INVALID_ELEMENT_STATE(12),
+ UNKNOWN_ERROR(13),
+ ELEMENT_NOT_SELECTABLE(15),
+ XPATH_LOOKUP_ERROR(19),
+ NO_SUCH_WINDOW(23),
+ INVALID_COOKIE_DOMAIN(24),
+ UNABLE_TO_SET_COOKIE(25),
+ MODAL_DIALOG_OPENED(26),
+ MODAL_DIALOG_OPEN(27),
+ SCRIPT_TIMEOUT(28);
+
+ private final int mCode;
+ private static ErrorCode[] values = ErrorCode.values();
+
+ ErrorCode(int code) {
+ this.mCode = code;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ public static ErrorCode get(final int intValue) {
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].getCode() == intValue) {
+ return values[i];
+ }
+ }
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ public WebDriver(WebView webview) {
+ this.mWebView = webview;
+ mWebView.requestFocus();
+ if (mWebView == null) {
+ throw new IllegalArgumentException("WebView cannot be null");
+ }
+ if (!mWebView.getSettings().getJavaScriptEnabled()) {
+ throw new RuntimeException("Javascript is disabled in the WebView. "
+ + "Enable it to use WebDriver");
+ }
+ shouldRunInMainThread(true);
+
+ mSyncObject = new Object();
+ mSyncPageLoad = new Object();
+ this.mWebView = webview;
+ WebChromeClientWrapper chromeWrapper = new WebChromeClientWrapper(
+ webview.getWebChromeClient(), this);
+ mWebView.setWebChromeClient(chromeWrapper);
+ WebViewClientWrapper viewWrapper = new WebViewClientWrapper(
+ webview.getWebViewClient(), this);
+ mWebView.setWebViewClient(viewWrapper);
+ mWebView.addJavascriptInterface(new JavascriptResultReady(),
+ "webdriver");
+ mDocumentElement = new WebElement(this, "");
+ mNavigation = new Navigation();
+ }
+
+ /**
+ * @return The title of the current page, null if not set.
+ */
+ public String getTitle() {
+ return mWebView.getTitle();
+ }
+
+ /**
+ * Loads a URL in the WebView. This function is blocking and will return
+ * when the page has finished loading.
+ *
+ * @param url The URL to load.
+ */
+ public void get(String url) {
+ mNavigation.to(url);
+ }
+
+ /**
+ * @return The source page of the currently loaded page in WebView.
+ */
+ public String getPageSource() {
+ return (String) executeScript("return new XMLSerializer()."
+ + "serializeToString(document);");
+ }
+
+ /**
+ * Find the first {@link android.webkit.webdriver.WebElement} using the
+ * given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return The first matching element on the current context.
+ * @throws {@link android.webkit.webdriver.WebElementNotFoundException} if
+ * no matching element was found.
+ */
+ public WebElement findElement(By by) {
+ checkNotNull(mDocumentElement, "Load a page using WebDriver.get() "
+ + "before looking for elements.");
+ return by.findElement(mDocumentElement);
+ }
+
+ /**
+ * Finds all {@link android.webkit.webdriver.WebElement} within the page
+ * using the given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return A list of all {@link android.webkit.webdriver.WebElement} found,
+ * or an empty list if nothing matches.
+ */
+ public List<WebElement> findElements(By by) {
+ checkNotNull(mDocumentElement, "Load a page using WebDriver.get() "
+ + "before looking for elements.");
+ return by.findElements(mDocumentElement);
+ }
+
+ /**
+ * Clears the WebView's state and closes associated views.
+ */
+ public void quit() {
+ mWebView.clearCache(true);
+ mWebView.clearFormData();
+ mWebView.clearHistory();
+ mWebView.clearSslPreferences();
+ mWebView.clearView();
+ mWebView.removeAllViewsInLayout();
+ }
+
+ /**
+ * Executes javascript in the context of the main frame.
+ *
+ * If the script has a return value the following happens:
+ * <ul>
+ * <li>For an HTML element, this method returns a WebElement</li>
+ * <li>For a decimal, a Double is returned</li>
+ * <li>For non-decimal number, a Long is returned</li>
+ * <li>For a boolean, a Boolean is returned</li>
+ * <li>For all other cases, a String is returned</li>
+ * <li>For an array, this returns a List<Object> with each object
+ * following the rules above.</li>
+ * <li>For an object literal this returns a Map<String, Object>. Note that
+ * Object literals keys can only be Strings. Non Strings keys will
+ * be filtered out.</li>
+ * </ul>
+ *
+ * <p> Arguments must be a number, a boolean, a string a WebElement or
+ * a list of any combination of the above. The arguments will be made
+ * available to the javascript via the "arguments" magic variable,
+ * as if the function was called via "Function.apply".
+ *
+ * @param script The JavaScript to execute.
+ * @param args The arguments to the script. Can be any of a number, boolean,
+ * string, WebElement or a List of those.
+ * @return A Boolean, Long, Double, String, WebElement, List or null.
+ */
+ public Object executeScript(final String script, final Object... args) {
+ String scriptArgs = "[" + convertToJsArgs(args) + "]";
+ String injectScriptJs = getResourceAsString(R.raw.execute_script_android);
+ return executeRawJavascript("(" + injectScriptJs +
+ ")(" + escapeAndQuote(script) + ", " + scriptArgs + ", true)");
+ }
+
+ public Navigation navigate() {
+ return mNavigation;
+ }
+
+
+ /**
+ * @hide
+ */
+ public class Navigation {
+ /* package */ Navigation () {}
+
+ public void back() {
+ navigate(CMD_NAV_BACK, null);
+ }
+
+ public void forward() {
+ navigate(CMD_NAV_FORWARD, null);
+ }
+
+ public void to(String url) {
+ navigate(CMD_GET_URL, url);
+ }
+
+ public void refresh() {
+ navigate(CMD_NAV_REFRESH, null);
+ }
+
+ private void navigate(int command, String url) {
+ synchronized (mSyncPageLoad) {
+ mPageFinishedLoading = false;
+ Message msg = mHandler.obtainMessage(command);
+ msg.obj = url;
+ mHandler.sendMessage(msg);
+ waitForPageLoad();
+ }
+ }
+ }
+
+ /**
+ * Converts the arguments passed to a JavaScript friendly format.
+ *
+ * @param args The arguments to convert.
+ * @return Comma separated Strings containing the arguments.
+ */
+ /* package */ String convertToJsArgs(final Object... args) {
+ StringBuilder toReturn = new StringBuilder();
+ int length = args.length;
+ for (int i = 0; i < length; i++) {
+ toReturn.append((i > 0) ? "," : "");
+ if (args[i] instanceof List<?>) {
+ toReturn.append("[");
+ List<Object> aList = (List<Object>) args[i];
+ for (int j = 0 ; j < aList.size(); j++) {
+ String comma = ((j == 0) ? "" : ",");
+ toReturn.append(comma + convertToJsArgs(aList.get(j)));
+ }
+ toReturn.append("]");
+ } else if (args[i] instanceof Map<?, ?>) {
+ Map<Object, Object> aMap = (Map<Object, Object>) args[i];
+ String toAdd = "{";
+ for (Object key: aMap.keySet()) {
+ toAdd += key + ":"
+ + convertToJsArgs(aMap.get(key)) + ",";
+ }
+ toReturn.append(toAdd.substring(0, toAdd.length() -1) + "}");
+ } else if (args[i] instanceof WebElement) {
+ // WebElement are represented in JavaScript by Objects as
+ // follow: {ELEMENT:"id"} where "id" refers to the id
+ // of the HTML element in the javascript cache that can
+ // be accessed throught bot.inject.cache.getCache_()
+ toReturn.append("{\"" + ELEMENT_KEY + "\":\""
+ + ((WebElement) args[i]).getId() + "\"}");
+ } else if (args[i] instanceof Number || args[i] instanceof Boolean) {
+ toReturn.append(String.valueOf(args[i]));
+ } else if (args[i] instanceof String) {
+ toReturn.append(escapeAndQuote((String) args[i]));
+ } else {
+ throw new IllegalArgumentException(
+ "Javascript arguments can be "
+ + "a Number, a Boolean, a String, a WebElement, "
+ + "or a List or a Map of those. Got: "
+ + ((args[i] == null) ? "null" : args[i].getClass()
+ + ", value: " + args[i].toString()));
+ }
+ }
+ return toReturn.toString();
+ }
+
+ /* package */ Object executeRawJavascript(final String script) {
+ if (mWebView.getUrl() == null) {
+ throw new WebDriverException("Cannot operate on a blank page. "
+ + "Load a page using WebDriver.get().");
+ }
+ String result = executeCommand(CMD_EXECUTE_SCRIPT,
+ "if (!window.webdriver || !window.webdriver.resultReady) {" +
+ " return;" +
+ "}" +
+ "window.webdriver.resultReady(" + script + ")",
+ JS_EXECUTION_TIMEOUT);
+ if (result == null || "undefined".equals(result)) {
+ return null;
+ }
+ try {
+ JSONObject json = new JSONObject(result);
+ throwIfError(json);
+ Object value = json.get(VALUE);
+ return convertJsonToJavaObject(value);
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JavaScript result: "
+ + result.toString(), e);
+ }
+ }
+
+ /* package */ String getResourceAsString(final int resourceId) {
+ InputStream is = mWebView.getResources().openRawResource(resourceId);
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ StringBuilder sb = new StringBuilder();
+ String line = null;
+ try {
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ }
+ br.close();
+ is.close();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to open JavaScript resource.", e);
+ }
+ return sb.toString();
+ }
+
+ /* package */ void sendTouchScreen(Point coords) {
+ // Reset state
+ resetPageLoadState();
+ executeCommand(CMD_SEND_TOUCH, coords,LOADING_TIMEOUT);
+ // Wait for the events to be fully handled
+ waitForMessageRelay(MOTION_EVENT_TIMEOUT);
+
+ // If a page started loading, block until page finishes loading
+ waitForPageLoadIfNeeded();
+ }
+
+ /* package */ void resetPageLoadState() {
+ synchronized (mSyncPageLoad) {
+ mPageStartedLoading = false;
+ mPageFinishedLoading = false;
+ }
+ }
+
+ /* package */ void waitForPageLoadIfNeeded() {
+ synchronized (mSyncPageLoad) {
+ Long end = System.currentTimeMillis() + PAGE_STARTED_LOADING;
+ // Wait PAGE_STARTED_LOADING milliseconds to see if we detect a
+ // page load.
+ while (!mPageStartedLoading && (System.currentTimeMillis() <= end)) {
+ try {
+ // This is notified by WebChromeClientWrapper#onProgressChanged
+ // when the page finished loading.
+ mSyncPageLoad.wait(PAGE_STARTED_LOADING);
+ } catch (InterruptedException e) {
+ new RuntimeException(e);
+ }
+ }
+ if (mPageStartedLoading) {
+ waitForPageLoad();
+ }
+ }
+ }
+
+ private void touchScreen(Point coords) {
+ // Convert to screen coords
+ // screen = JS x zoom - offset
+ float zoom = mWebView.getScale();
+ float xOffset = mWebView.getX();
+ float yOffset = mWebView.getY();
+ Point screenCoords = new Point( (int)(coords.x*zoom - xOffset),
+ (int)(coords.y*zoom - yOffset));
+
+ long downTime = SystemClock.uptimeMillis();
+ MotionEvent down = MotionEvent.obtain(downTime,
+ SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, screenCoords.x,
+ screenCoords.y, 0);
+ down.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ MotionEvent up = MotionEvent.obtain(downTime,
+ SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, screenCoords.x,
+ screenCoords.y, 0);
+ up.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ // Dispatch the events to WebView
+ mWebView.dispatchTouchEvent(down);
+ mWebView.dispatchTouchEvent(up);
+ }
+
+ /* package */ void notifyPageStartedLoading() {
+ synchronized (mSyncPageLoad) {
+ mPageStartedLoading = true;
+ mSyncPageLoad.notify();
+ }
+ }
+
+ /* package */ void notifyPageFinishedLoading() {
+ synchronized (mSyncPageLoad) {
+ mPageFinishedLoading = true;
+ mSyncPageLoad.notify();
+ }
+ }
+
+ /**
+ *
+ * @param keys The first element of the CharSequence should be the
+ * existing value in the text input, or the empty string if none.
+ */
+ /* package */ void sendKeys(CharSequence[] keys) {
+ executeCommand(CMD_SEND_KEYS, keys, KEY_EVENT_TIMEOUT);
+ // Wait for all KeyEvents to be handled
+ waitForMessageRelay(KEY_EVENT_TIMEOUT);
+ }
+
+ /* package */ void sendKeyCodes(int[] keycodes) {
+ executeCommand(CMD_SEND_KEYCODE, keycodes, KEY_EVENT_TIMEOUT);
+ // Wait for all KeyEvents to be handled
+ waitForMessageRelay(KEY_EVENT_TIMEOUT);
+ }
+
+ /* package */ void moveCursorToRightMostPosition(String value) {
+ executeCommand(CMD_MOVE_CURSOR_RIGHTMOST_POS, value, KEY_EVENT_TIMEOUT);
+ waitForMessageRelay(KEY_EVENT_TIMEOUT);
+ }
+
+ private void moveCursorToLeftMostPos(String value) {
+ // If there is text, move the cursor to the rightmost position
+ if (value != null && !value.equals("")) {
+ long downTime = SystemClock.uptimeMillis();
+ KeyEvent down = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, 0);
+ KeyEvent up = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_RIGHT,
+ value.length());
+ mWebView.dispatchKeyEvent(down);
+ mWebView.dispatchKeyEvent(up);
+ }
+ }
+
+ private void dispatchKeyCodes(int[] keycodes) {
+ for (int i = 0; i < keycodes.length; i++) {
+ KeyEvent down = new KeyEvent(KeyEvent.ACTION_DOWN, keycodes[i]);
+ KeyEvent up = new KeyEvent(KeyEvent.ACTION_UP, keycodes[i]);
+ mWebView.dispatchKeyEvent(down);
+ mWebView.dispatchKeyEvent(up);
+ }
+ }
+
+ private void dispatchKeys(CharSequence[] keys) {
+ KeyCharacterMap chararcterMap = KeyCharacterMap.load(
+ KeyCharacterMap.VIRTUAL_KEYBOARD);
+ for (int i = 0; i < keys.length; i++) {
+ CharSequence s = keys[i];
+ for (int j = 0; j < s.length(); j++) {
+ KeyEvent[] events =
+ chararcterMap.getEvents(new char[]{s.charAt(j)});
+ for (KeyEvent e : events) {
+ mWebView.dispatchKeyEvent(e);
+ }
+ }
+ }
+ }
+
+ private void waitForMessageRelay(long timeout) {
+ synchronized (mSyncObject) {
+ mCommandDone = false;
+ }
+ Message msg = Message.obtain();
+ msg.what = WebViewCore.EventHub.MESSAGE_RELAY;
+ Message echo = mHandler.obtainMessage(CMD_MESSAGE_RELAY_ECHO);
+ msg.obj = echo;
+
+ mWebView.getWebViewCore().sendMessage(msg);
+ synchronized (mSyncObject) {
+ long end = System.currentTimeMillis() + timeout;
+ while (!mCommandDone && (System.currentTimeMillis() <= end)) {
+ try {
+ // This is notifed by the mHandler when it receives the
+ // MESSAGE_RELAY back
+ mSyncObject.wait(timeout);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ private void waitForPageLoad() {
+ long endLoad = System.currentTimeMillis() + LOADING_TIMEOUT;
+ while (!mPageFinishedLoading
+ && (System.currentTimeMillis() <= endLoad)) {
+ try {
+ mSyncPageLoad.wait(LOADING_TIMEOUT);
+ } catch (InterruptedException e) {
+ throw new RuntimeException();
+ }
+ }
+ }
+
+ /**
+ * Wraps the given string into quotes and escape existing quotes
+ * and backslashes.
+ * "foo" -> "\"foo\""
+ * "foo\"" -> "\"foo\\\"\""
+ * "fo\o" -> "\"fo\\o\""
+ *
+ * @param toWrap The String to wrap in quotes
+ * @return a String wrapping the original String in quotes
+ */
+ private static String escapeAndQuote(final String toWrap) {
+ StringBuilder toReturn = new StringBuilder("\"");
+ for (int i = 0; i < toWrap.length(); i++) {
+ char c = toWrap.charAt(i);
+ if (c == '\"') {
+ toReturn.append("\\\"");
+ } else if (c == '\\') {
+ toReturn.append("\\\\");
+ } else {
+ toReturn.append(c);
+ }
+ }
+ toReturn.append("\"");
+ return toReturn.toString();
+ }
+
+ private Object convertJsonToJavaObject(final Object toConvert) {
+ try {
+ if (toConvert == null
+ || toConvert.equals(null)
+ || "undefined".equals(toConvert)
+ || "null".equals(toConvert)) {
+ return null;
+ } else if (toConvert instanceof Boolean) {
+ return toConvert;
+ } else if (toConvert instanceof Double
+ || toConvert instanceof Float) {
+ return Double.valueOf(String.valueOf(toConvert));
+ } else if (toConvert instanceof Integer
+ || toConvert instanceof Long) {
+ return Long.valueOf(String.valueOf(toConvert));
+ } else if (toConvert instanceof JSONArray) { // List
+ return convertJsonArrayToList((JSONArray) toConvert);
+ } else if (toConvert instanceof JSONObject) { // Map or WebElment
+ JSONObject map = (JSONObject) toConvert;
+ if (map.opt(ELEMENT_KEY) != null) { // WebElement
+ return new WebElement(this, (String) map.get(ELEMENT_KEY));
+ } else { // Map
+ return convertJsonObjectToMap(map);
+ }
+ } else {
+ return toConvert.toString();
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JavaScript result: "
+ + toConvert.toString(), e);
+ }
+ }
+
+ private List<Object> convertJsonArrayToList(final JSONArray json) {
+ List<Object> toReturn = Lists.newArrayList();
+ for (int i = 0; i < json.length(); i++) {
+ try {
+ toReturn.add(convertJsonToJavaObject(json.get(i)));
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JSON: "
+ + json.toString(), e);
+ }
+ }
+ return toReturn;
+ }
+
+ private Map<Object, Object> convertJsonObjectToMap(final JSONObject json) {
+ Map<Object, Object> toReturn = Maps.newHashMap();
+ for (Iterator it = json.keys(); it.hasNext();) {
+ String key = (String) it.next();
+ try {
+ Object value = json.get(key);
+ toReturn.put(convertJsonToJavaObject(key),
+ convertJsonToJavaObject(value));
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JSON:"
+ + json.toString(), e);
+ }
+ }
+ return toReturn;
+ }
+
+ private void throwIfError(final JSONObject jsonObject) {
+ ErrorCode status;
+ String errorMsg;
+ try {
+ status = ErrorCode.get((Integer) jsonObject.get(STATUS));
+ errorMsg = String.valueOf(jsonObject.get(VALUE));
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JSON Object: "
+ + jsonObject, e);
+ }
+ switch (status) {
+ case SUCCESS:
+ return;
+ case NO_SUCH_ELEMENT:
+ throw new WebElementNotFoundException("Could not find "
+ + "WebElement.");
+ case STALE_ELEMENT_REFERENCE:
+ throw new WebElementStaleException("WebElement is stale.");
+ default:
+ throw new WebDriverException("Error: " + errorMsg);
+ }
+ }
+
+ private void shouldRunInMainThread(boolean value) {
+ assert (value == (MAIN_THREAD == Thread.currentThread().getId()));
+ }
+
+ /**
+ * Interface called from JavaScript when the result is ready.
+ */
+ private class JavascriptResultReady {
+
+ /**
+ * A callback from JavaScript to Java that passes the result as a
+ * parameter. This method is available from the WebView's
+ * JavaScript DOM as window.webdriver.resultReady().
+ *
+ * @param result The result that should be sent to Java from Javascript.
+ */
+ public void resultReady(final String result) {
+ synchronized (mSyncObject) {
+ mJsResult = result;
+ mCommandDone = true;
+ mSyncObject.notify();
+ }
+ }
+ }
+
+ /* package */ void notifyCommandDone() {
+ synchronized (mSyncObject) {
+ mCommandDone = true;
+ mSyncObject.notify();
+ }
+ }
+
+ /**
+ * Executes the given command by posting a message to mHandler. This thread
+ * will block until the command which runs in the main thread is done.
+ *
+ * @param command The command to run.
+ * @param arg The argument for that command.
+ * @param timeout A timeout in milliseconds.
+ */
+ private String executeCommand(int command, final Object arg, long timeout) {
+ shouldRunInMainThread(false);
+ synchronized (mSyncObject) {
+ mCommandDone = false;
+ Message msg = mHandler.obtainMessage(command);
+ msg.obj = arg;
+ mHandler.sendMessage(msg);
+
+ long end = System.currentTimeMillis() + timeout;
+ while (!mCommandDone) {
+ if (System.currentTimeMillis() >= end) {
+ throw new RuntimeException("Timeout executing command: "
+ + command);
+ }
+ try {
+ mSyncObject.wait(timeout);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return mJsResult;
+ }
+
+ private void checkNotNull(Object obj, String errosMsg) {
+ if (obj == null) {
+ throw new NullPointerException(errosMsg);
+ }
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebDriverException.java b/core/java/android/webkit/webdriver/WebDriverException.java
new file mode 100644
index 0000000..1a579c2
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebDriverException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+/**
+ * @hide
+ */
+public class WebDriverException extends RuntimeException {
+ public WebDriverException() {
+ super();
+ }
+
+ public WebDriverException(String reason) {
+ super(reason);
+ }
+
+ public WebDriverException(String reason, Throwable cause) {
+ super(reason, cause);
+ }
+
+ public WebDriverException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebElement.java b/core/java/android/webkit/webdriver/WebElement.java
new file mode 100644
index 0000000..02c1595
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElement.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.graphics.Point;
+import android.view.KeyEvent;
+
+import com.android.internal.R;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents an HTML element. Typically most interactions with a web page
+ * will be performed through this class.
+ *
+ * @hide
+ */
+public class WebElement {
+ private final String mId;
+ private final WebDriver mDriver;
+
+ private static final String LOCATOR_ID = "id";
+ private static final String LOCATOR_LINK_TEXT = "linkText";
+ private static final String LOCATOR_PARTIAL_LINK_TEXT = "partialLinkText";
+ private static final String LOCATOR_NAME = "name";
+ private static final String LOCATOR_CLASS_NAME = "className";
+ private static final String LOCATOR_CSS = "css";
+ private static final String LOCATOR_TAG_NAME = "tagName";
+ private static final String LOCATOR_XPATH = "xpath";
+
+ /**
+ * Package constructor to prevent clients from creating a new WebElement
+ * instance.
+ *
+ * <p> A WebElement represents an HTML element on the page.
+ * The corresponding HTML element is stored in a JS cache in the page
+ * that can be accessed through JavaScript using "bot.inject.cache".
+ *
+ * @param driver The WebDriver instance to use.
+ * @param id The index of the HTML element in the JavaSctipt cache.
+ * document.documentElement object.
+ */
+ /* package */ WebElement(final WebDriver driver, final String id) {
+ this.mId = id;
+ this.mDriver = driver;
+ }
+
+ /**
+ * Finds the first {@link android.webkit.webdriver.WebElement} using the
+ * given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return The first matching element on the current context.
+ */
+ public WebElement findElement(final By by) {
+ return by.findElement(this);
+ }
+
+ /**
+ * Finds all {@link android.webkit.webdriver.WebElement} within the page
+ * using the given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return A list of all {@link android.webkit.webdriver.WebElement} found,
+ * or an empty list if nothing matches.
+ */
+ public List<WebElement> findElements(final By by) {
+ return by.findElements(this);
+ }
+
+ /**
+ * Gets the visisble (i.e. not hidden by CSS) innerText of this element,
+ * inlcuding sub-elements.
+ *
+ * @return the innerText of this element.
+ * @throws {@link android.webkit.webdriver.WebElementStaleException} if this
+ * element is stale, i.e. not on the current DOM.
+ */
+ public String getText() {
+ String getText = mDriver.getResourceAsString(R.raw.get_text_android);
+ return (String) executeAtom(getText, this);
+ }
+
+ /**
+ * Gets the value of an HTML attribute for this element or the value of the
+ * property with the same name if the attribute is not present. If neither
+ * is set, null is returned.
+ *
+ * @param attribute the HTML attribute.
+ * @return the value of that attribute or the value of the property with the
+ * same name if the attribute is not set, or null if neither are set. For
+ * boolean attribute values this will return the string "true" or "false".
+ */
+ public String getAttribute(String attribute) {
+ String getAttribute = mDriver.getResourceAsString(
+ R.raw.get_attribute_value_android);
+ return (String) executeAtom(getAttribute, this, attribute);
+ }
+
+ /**
+ * @return the tag name of this element.
+ */
+ public String getTagName() {
+ return (String) mDriver.executeScript("return arguments[0].tagName;",
+ this);
+ }
+
+ /**
+ * @return true if this element is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ String isEnabled = mDriver.getResourceAsString(
+ R.raw.is_enabled_android);
+ return (Boolean) executeAtom(isEnabled, this);
+ }
+
+ /**
+ * Determines whether this element is selected or not. This applies to input
+ * elements such as checkboxes, options in a select, and radio buttons.
+ *
+ * @return True if this element is selected, false otherwise.
+ */
+ public boolean isSelected() {
+ String isSelected = mDriver.getResourceAsString(
+ R.raw.is_selected_android);
+ return (Boolean) executeAtom(isSelected, this);
+ }
+
+ /**
+ * Selects an element on the page. This works for selecting checkboxes,
+ * options in a select, and radio buttons.
+ */
+ public void setSelected() {
+ String setSelected = mDriver.getResourceAsString(
+ R.raw.set_selected_android);
+ executeAtom(setSelected, this);
+ }
+
+ /**
+ * This toggles the checkboxe state from selected to not selected, or
+ * from not selected to selected.
+ *
+ * @return True if the toggled element is selected, false otherwise.
+ */
+ public boolean toggle() {
+ String toggle = mDriver.getResourceAsString(R.raw.toggle_android);
+ return (Boolean) executeAtom(toggle, this);
+ }
+
+ /**
+ * Sends the KeyEvents for the given sequence of characters to the
+ * WebElement to simulate typing. The KeyEvents are generated using the
+ * device's {@link android.view.KeyCharacterMap.VIRTUAL_KEYBOARD}.
+ *
+ * @param keys The keys to send to this WebElement
+ */
+ public void sendKeys(CharSequence... keys) {
+ if (keys == null || keys.length == 0) {
+ return;
+ }
+ click();
+ mDriver.moveCursorToRightMostPosition(getAttribute("value"));
+ mDriver.sendKeys(keys);
+ }
+
+ /**
+ * Use this to send one of the key code constants defined in
+ * {@link android.view.KeyEvent}
+ *
+ * @param keys
+ */
+ public void sendKeyCodes(int... keys) {
+ if (keys == null || keys.length == 0) {
+ return;
+ }
+ click();
+ mDriver.moveCursorToRightMostPosition(getAttribute("value"));
+ mDriver.sendKeyCodes(keys);
+ }
+
+ /**
+ * Sends a touch event to the center coordinates of this WebElement.
+ */
+ public void click() {
+ Point topLeft = getLocation();
+ Point size = getSize();
+ int jsX = topLeft.x + size.x/2;
+ int jsY = topLeft.y + size.y/2;
+ Point center = new Point(jsX, jsY);
+ mDriver.sendTouchScreen(center);
+ }
+
+ /**
+ * Submits the form containing this WebElement.
+ */
+ public void submit() {
+ mDriver.resetPageLoadState();
+ String submit = mDriver.getResourceAsString(R.raw.submit_android);
+ executeAtom(submit, this);
+ mDriver.waitForPageLoadIfNeeded();
+ }
+
+ /**
+ * Clears the text value if this is a text entry element. Does nothing
+ * otherwise.
+ */
+ public void clear() {
+ String value = getAttribute("value");
+ if (value == null || value.equals("")) {
+ return;
+ }
+ int length = value.length();
+ int[] keys = new int[length];
+ for (int i = 0; i < length; i++) {
+ keys[i] = KeyEvent.KEYCODE_DEL;
+ }
+ sendKeyCodes(keys);
+ }
+
+ /**
+ * @return the value of the given CSS property if found, null otherwise.
+ */
+ public String getCssValue(String cssProperty) {
+ String getCssProp = mDriver.getResourceAsString(
+ R.raw.get_value_of_css_property_android);
+ return (String) executeAtom(getCssProp, this, cssProperty);
+ }
+
+ /**
+ * Gets the width and height of the rendered element.
+ *
+ * @return a {@link android.graphics.Point}, where Point.x represents the
+ * width, and Point.y represents the height of the element.
+ */
+ public Point getSize() {
+ String getSize = mDriver.getResourceAsString(R.raw.get_size_android);
+ Map<String, Long> map = (Map<String, Long>) executeAtom(getSize, this);
+ return new Point(map.get("width").intValue(),
+ map.get("height").intValue());
+ }
+
+ /**
+ * Gets the location of the top left corner of this element on the screen.
+ * If the element is not visisble, this will scroll to get the element into
+ * the visisble screen.
+ *
+ * @return a {@link android.graphics.Point} containing the x and y
+ * coordinates of the top left corner of this element.
+ */
+ public Point getLocation() {
+ String getLocation = mDriver.getResourceAsString(
+ R.raw.get_top_left_coordinates_android);
+ Map<String,Long> map = (Map<String, Long>) executeAtom(getLocation,
+ this);
+ return new Point(map.get("x").intValue(), map.get("y").intValue());
+ }
+
+ /**
+ * @return True if the WebElement is displayed on the screen,
+ * false otherwise.
+ */
+ public boolean isDisplayed() {
+ String isDisplayed = mDriver.getResourceAsString(
+ R.raw.is_displayed_android);
+ return (Boolean) executeAtom(isDisplayed, this);
+ }
+
+ /*package*/ String getId() {
+ return mId;
+ }
+
+ /* package */ WebElement findElementById(final String locator) {
+ return findElement(LOCATOR_ID, locator);
+ }
+
+ /* package */ WebElement findElementByLinkText(final String linkText) {
+ return findElement(LOCATOR_LINK_TEXT, linkText);
+ }
+
+ /* package */ WebElement findElementByPartialLinkText(
+ final String linkText) {
+ return findElement(LOCATOR_PARTIAL_LINK_TEXT, linkText);
+ }
+
+ /* package */ WebElement findElementByName(final String name) {
+ return findElement(LOCATOR_NAME, name);
+ }
+
+ /* package */ WebElement findElementByClassName(final String className) {
+ return findElement(LOCATOR_CLASS_NAME, className);
+ }
+
+ /* package */ WebElement findElementByCss(final String css) {
+ return findElement(LOCATOR_CSS, css);
+ }
+
+ /* package */ WebElement findElementByTagName(final String tagName) {
+ return findElement(LOCATOR_TAG_NAME, tagName);
+ }
+
+ /* package */ WebElement findElementByXPath(final String xpath) {
+ return findElement(LOCATOR_XPATH, xpath);
+ }
+
+ /* package */ List<WebElement> findElementsById(final String locator) {
+ return findElements(LOCATOR_ID, locator);
+ }
+
+ /* package */ List<WebElement> findElementsByLinkText(final String linkText) {
+ return findElements(LOCATOR_LINK_TEXT, linkText);
+ }
+
+ /* package */ List<WebElement> findElementsByPartialLinkText(
+ final String linkText) {
+ return findElements(LOCATOR_PARTIAL_LINK_TEXT, linkText);
+ }
+
+ /* package */ List<WebElement> findElementsByName(final String name) {
+ return findElements(LOCATOR_NAME, name);
+ }
+
+ /* package */ List<WebElement> findElementsByClassName(final String className) {
+ return findElements(LOCATOR_CLASS_NAME, className);
+ }
+
+ /* package */ List<WebElement> findElementsByCss(final String css) {
+ return findElements(LOCATOR_CSS, css);
+ }
+
+ /* package */ List<WebElement> findElementsByTagName(final String tagName) {
+ return findElements(LOCATOR_TAG_NAME, tagName);
+ }
+
+ /* package */ List<WebElement> findElementsByXPath(final String xpath) {
+ return findElements(LOCATOR_XPATH, xpath);
+ }
+
+ private Object executeAtom(final String atom, final Object... args) {
+ String scriptArgs = mDriver.convertToJsArgs(args);
+ return mDriver.executeRawJavascript("(" +
+ atom + ")(" + scriptArgs + ")");
+ }
+
+ private List<WebElement> findElements(String strategy, String locator) {
+ String findElements = mDriver.getResourceAsString(
+ R.raw.find_elements_android);
+ if (mId.equals("")) {
+ return (List<WebElement>) executeAtom(findElements,
+ strategy, locator);
+ } else {
+ return (List<WebElement>) executeAtom(findElements,
+ strategy, locator, this);
+ }
+ }
+
+ private WebElement findElement(String strategy, String locator) {
+ String findElement = mDriver.getResourceAsString(
+ R.raw.find_element_android);
+ WebElement el;
+ if (mId.equals("")) {
+ el = (WebElement) executeAtom(findElement,
+ strategy, locator);
+ } else {
+ el = (WebElement) executeAtom(findElement,
+ strategy, locator, this);
+ }
+ if (el == null) {
+ throw new WebElementNotFoundException("Could not find element "
+ + "with " + strategy + ": " + locator);
+ }
+ return el;
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebElementNotFoundException.java b/core/java/android/webkit/webdriver/WebElementNotFoundException.java
new file mode 100644
index 0000000..e66d279
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElementNotFoundException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+/**
+ * Thrown when a {@link android.webkit.webdriver.WebElement} is not found in the
+ * DOM of the page.
+ * @hide
+ */
+public class WebElementNotFoundException extends RuntimeException {
+
+ public WebElementNotFoundException() {
+ super();
+ }
+
+ public WebElementNotFoundException(String reason) {
+ super(reason);
+ }
+
+ public WebElementNotFoundException(String reason, Throwable cause) {
+ super(reason, cause);
+ }
+
+ public WebElementNotFoundException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebElementStaleException.java b/core/java/android/webkit/webdriver/WebElementStaleException.java
new file mode 100644
index 0000000..c59e794
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElementStaleException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+/**
+ * Thrown when trying to access a {@link android.webkit.webdriver.WebElement}
+ * that is stale. This mean that the {@link android.webkit.webdriver.WebElement}
+ * is no longer present on the DOM of the page.
+ * @hide
+ */
+public class WebElementStaleException extends RuntimeException {
+
+ public WebElementStaleException() {
+ super();
+ }
+
+ public WebElementStaleException(String reason) {
+ super(reason);
+ }
+
+ public WebElementStaleException(String reason, Throwable cause) {
+ super(reason, cause);
+ }
+
+ public WebElementStaleException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebViewClient.java b/core/java/android/webkit/webdriver/WebViewClient.java
new file mode 100644
index 0000000..c582b24
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebViewClient.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.graphics.Bitmap;
+import android.net.http.SslError;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+/* package */ class WebViewClientWrapper extends WebViewClient {
+ private final WebViewClient mDelegate;
+ private final WebDriver mDriver;
+
+ public WebViewClientWrapper(WebViewClient delegate, WebDriver driver) {
+ if (delegate == null) {
+ mDelegate = new WebViewClient();
+ } else {
+ mDelegate = delegate;
+ }
+ this.mDriver = driver;
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ return mDelegate.shouldOverrideUrlLoading(view, url);
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ mDriver.notifyPageStartedLoading();
+ mDelegate.onPageStarted(view, url, favicon);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ mDelegate.onPageFinished(view, url);
+ }
+
+ @Override
+ public void onLoadResource(WebView view, String url) {
+ mDelegate.onLoadResource(view, url);
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view,
+ String url) {
+ return mDelegate.shouldInterceptRequest(view, url);
+ }
+
+ @Override
+ public void onTooManyRedirects(WebView view, Message cancelMsg,
+ Message continueMsg) {
+ mDelegate.onTooManyRedirects(view, cancelMsg, continueMsg);
+ }
+
+ @Override
+ public void onReceivedError(WebView view, int errorCode, String description,
+ String failingUrl) {
+ mDelegate.onReceivedError(view, errorCode, description, failingUrl);
+ }
+
+ @Override
+ public void onFormResubmission(WebView view, Message dontResend,
+ Message resend) {
+ mDelegate.onFormResubmission(view, dontResend, resend);
+ }
+
+ @Override
+ public void doUpdateVisitedHistory(WebView view, String url,
+ boolean isReload) {
+ mDelegate.doUpdateVisitedHistory(view, url, isReload);
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler,
+ SslError error) {
+ mDelegate.onReceivedSslError(view, handler, error);
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
+ String host, String realm) {
+ mDelegate.onReceivedHttpAuthRequest(view, handler, host, realm);
+ }
+
+ @Override
+ public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
+ return mDelegate.shouldOverrideKeyEvent(view, event);
+ }
+
+ @Override
+ public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
+ mDelegate.onUnhandledKeyEvent(view, event);
+ }
+
+ @Override
+ public void onScaleChanged(WebView view, float oldScale, float newScale) {
+ mDelegate.onScaleChanged(view, oldScale, newScale);
+ }
+
+ @Override
+ public void onReceivedLoginRequest(WebView view, String realm,
+ String account, String args) {
+ mDelegate.onReceivedLoginRequest(view, realm, account, args);
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebchromeClientWrapper.java b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
new file mode 100644
index 0000000..a9e5d19
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Message;
+import android.view.View;
+import android.webkit.ConsoleMessage;
+import android.webkit.GeolocationPermissions;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+
+/* package */ class WebChromeClientWrapper extends WebChromeClient {
+
+ private final WebChromeClient mDelegate;
+ private final WebDriver mDriver;
+
+ public WebChromeClientWrapper(WebChromeClient delegate, WebDriver driver) {
+ if (delegate == null) {
+ this.mDelegate = new WebChromeClient();
+ } else {
+ this.mDelegate = delegate;
+ }
+ this.mDriver = driver;
+ }
+
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ if (newProgress == 100) {
+ mDriver.notifyPageFinishedLoading();
+ }
+ mDelegate.onProgressChanged(view, newProgress);
+ }
+
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ mDelegate.onReceivedTitle(view, title);
+ }
+
+ @Override
+ public void onReceivedIcon(WebView view, Bitmap icon) {
+ mDelegate.onReceivedIcon(view, icon);
+ }
+
+ @Override
+ public void onReceivedTouchIconUrl(WebView view, String url,
+ boolean precomposed) {
+ mDelegate.onReceivedTouchIconUrl(view, url, precomposed);
+ }
+
+ @Override
+ public void onShowCustomView(View view,
+ CustomViewCallback callback) {
+ mDelegate.onShowCustomView(view, callback);
+ }
+
+ @Override
+ public void onHideCustomView() {
+ mDelegate.onHideCustomView();
+ }
+
+ @Override
+ public boolean onCreateWindow(WebView view, boolean dialog,
+ boolean userGesture, Message resultMsg) {
+ return mDelegate.onCreateWindow(view, dialog, userGesture, resultMsg);
+ }
+
+ @Override
+ public void onRequestFocus(WebView view) {
+ mDelegate.onRequestFocus(view);
+ }
+
+ @Override
+ public void onCloseWindow(WebView window) {
+ mDelegate.onCloseWindow(window);
+ }
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsAlert(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsConfirm(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message,
+ String defaultValue, JsPromptResult result) {
+ return mDelegate.onJsPrompt(view, url, message, defaultValue, result);
+ }
+
+ @Override
+ public boolean onJsBeforeUnload(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsBeforeUnload(view, url, message, result);
+ }
+
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier,
+ long currentQuota, long estimatedSize, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota,
+ estimatedSize, totalUsedQuota, quotaUpdater);
+ }
+
+ @Override
+ public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ mDelegate.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
+ quotaUpdater);
+ }
+
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin,
+ GeolocationPermissions.Callback callback) {
+ mDelegate.onGeolocationPermissionsShowPrompt(origin, callback);
+ }
+
+ @Override
+ public void onGeolocationPermissionsHidePrompt() {
+ mDelegate.onGeolocationPermissionsHidePrompt();
+ }
+
+ @Override
+ public boolean onJsTimeout() {
+ return mDelegate.onJsTimeout();
+ }
+
+ @Override
+ public void onConsoleMessage(String message, int lineNumber,
+ String sourceID) {
+ mDelegate.onConsoleMessage(message, lineNumber, sourceID);
+ }
+
+ @Override
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+ return mDelegate.onConsoleMessage(consoleMessage);
+ }
+
+ @Override
+ public Bitmap getDefaultVideoPoster() {
+ return mDelegate.getDefaultVideoPoster();
+ }
+
+ @Override
+ public View getVideoLoadingProgressView() {
+ return mDelegate.getVideoLoadingProgressView();
+ }
+
+ @Override
+ public void getVisitedHistory(ValueCallback<String[]> callback) {
+ mDelegate.getVisitedHistory(callback);
+ }
+
+ @Override
+ public void openFileChooser(ValueCallback<Uri> uploadFile,
+ String acceptType) {
+ mDelegate.openFileChooser(uploadFile, acceptType);
+ }
+
+ @Override
+ public void setInstallableWebApp() {
+ mDelegate.setInstallableWebApp();
+ }
+
+ @Override
+ public void setupAutoFill(Message msg) {
+ mDelegate.setupAutoFill(msg);
+ }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 094f195..82dd5db 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -55,6 +55,7 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -2532,6 +2533,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return mContextMenuInfo;
}
+ /** @hide */
+ @Override
+ public boolean showContextMenu(float x, float y, int metaState) {
+ final int position = pointToPosition((int)x, (int)y);
+ if (position != INVALID_POSITION) {
+ final long id = mAdapter.getItemId(position);
+ View child = getChildAt(position - mFirstPosition);
+ if (child != null) {
+ mContextMenuInfo = createContextMenuInfo(child, position, id);
+ return super.showContextMenuForChild(AbsListView.this);
+ }
+ }
+ return super.showContextMenu(x, y, metaState);
+ }
+
@Override
public boolean showContextMenuForChild(View originalView) {
final int longPressPosition = getPositionForView(originalView);
@@ -2806,7 +2822,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
// If we couldn't find a view to click on, but the down event
// was touching the edge, we will bail out and try again.
- // This allows the edge correcting code in ViewRoot to try to
+ // This allows the edge correcting code in ViewAncestor to try to
// find a nearby view to select
return false;
}
@@ -2834,6 +2850,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
break;
}
}
+
+ if (performButtonActionOnTouchDown(ev)) {
+ if (mTouchMode == TOUCH_MODE_DOWN) {
+ removeCallbacks(mPendingCheckForTap);
+ }
+ }
break;
}
@@ -4529,8 +4551,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Otherwise resurrects the selection and returns true if resurrected.
*/
boolean resurrectSelectionIfNeeded() {
- if (mSelectedPosition < 0) {
- return resurrectSelection();
+ if (mSelectedPosition < 0 && resurrectSelection()) {
+ updateSelectorState();
+ return true;
}
return false;
}
@@ -5009,7 +5032,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public boolean sendKeyEvent(KeyEvent event) {
// Use our own input connection, since the filter
// text view may not be shown in a window so has
- // no ViewRoot to dispatch events with.
+ // no ViewAncestor to dispatch events with.
return mDefInputConnection.sendKeyEvent(event);
}
};
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 0da73a4..2621e64 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -201,7 +201,8 @@ public abstract class AbsSeekBar extends ProgressBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
+ void onProgressRefresh(float scale, boolean fromUser) {
+ super.onProgressRefresh(scale, fromUser);
Drawable thumb = mThumb;
if (thumb != null) {
setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java
index ac82af7..7df6aab 100644
--- a/core/java/android/widget/AbsoluteLayout.java
+++ b/core/java/android/widget/AbsoluteLayout.java
@@ -141,6 +141,11 @@ public class AbsoluteLayout extends ViewGroup {
return new LayoutParams(p);
}
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
/**
* Per-child layout information associated with AbsoluteLayout.
* See
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index f16efbd..c4d05e9 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -876,7 +876,6 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = false;
// This is an exceptional case which occurs when a window gets the
// focus and sends a focus event via its focused child to announce
// current focus/selection. AdapterView fires selection but not focus
@@ -885,22 +884,43 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
- // we send selection events only from AdapterView to avoid
- // generation of such event for each child
+ // We first get a chance to populate the event.
+ onPopulateAccessibilityEvent(event);
+
+ return false;
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ // We send selection events only from AdapterView to avoid
+ // generation of such event for each child.
View selectedView = getSelectedView();
if (selectedView != null) {
- populated = selectedView.dispatchPopulateAccessibilityEvent(event);
+ selectedView.dispatchPopulateAccessibilityEvent(event);
}
+ }
- if (!populated) {
- if (selectedView != null) {
- event.setEnabled(selectedView.isEnabled());
- }
- event.setItemCount(getCount());
- event.setCurrentItemIndex(getSelectedItemPosition());
- }
+ @Override
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ // Add a record for ourselves as well.
+ AccessibilityEvent record = AccessibilityEvent.obtain();
+ // Set the class since it is not populated in #dispatchPopulateAccessibilityEvent
+ record.setClassName(getClass().getName());
+ child.dispatchPopulateAccessibilityEvent(record);
+ event.appendRecord(record);
+ return true;
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
- return populated;
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ event.setEnabled(selectedView.isEnabled());
+ }
+ event.setItemCount(getCount());
+ event.setCurrentItemIndex(getSelectedItemPosition());
}
@Override
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 072992e..c773527 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -79,7 +79,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
/**
* Map of the children of the {@link AdapterViewAnimator}.
*/
- HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
+ HashMap<Integer, ViewAndMetaData> mViewsMap = new HashMap<Integer, ViewAndMetaData>();
/**
* List of views pending removal from the {@link AdapterViewAnimator}
@@ -103,11 +103,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
int mCurrentWindowStartUnbounded = 0;
/**
- * Handler to post events to the main thread
- */
- Handler mMainQueue;
-
- /**
* Listens for data changes from the adapter
*/
AdapterDataSetObserver mDataSetObserver;
@@ -163,15 +158,18 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
private static final int DEFAULT_ANIMATION_DURATION = 200;
public AdapterViewAnimator(Context context) {
- super(context);
- initViewAnimator();
+ this(context, null);
}
public AdapterViewAnimator(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AdapterViewAnimator);
+ com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, 0);
int resource = a.getResourceId(
com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
@@ -203,17 +201,21 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* Initialize this {@link AdapterViewAnimator}
*/
private void initViewAnimator() {
- mMainQueue = new Handler(Looper.myLooper());
mPreviousViews = new ArrayList<Integer>();
}
- class ViewAndIndex {
- ViewAndIndex(View v, int i) {
- view = v;
- index = i;
- }
+ class ViewAndMetaData {
View view;
- int index;
+ int relativeIndex;
+ int adapterPosition;
+ long itemId;
+
+ ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId) {
+ this.view = view;
+ this.relativeIndex = relativeIndex;
+ this.adapterPosition = adapterPosition;
+ this.itemId = itemId;
+ }
}
/**
@@ -379,6 +381,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
}
+ private ViewAndMetaData getMetaDataForChild(View child) {
+ for (ViewAndMetaData vm: mViewsMap.values()) {
+ if (vm.view == child) {
+ return vm;
+ }
+ }
+ return null;
+ }
+
LayoutParams createOrReuseLayoutParams(View v) {
final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
if (currentLp instanceof ViewGroup.LayoutParams) {
@@ -481,7 +492,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (remove) {
View previousView = mViewsMap.get(index).view;
- int oldRelativeIndex = mViewsMap.get(index).index;
+ int oldRelativeIndex = mViewsMap.get(index).relativeIndex;
mPreviousViews.add(index);
transformViewForTransition(oldRelativeIndex, -1, previousView, animate);
@@ -497,7 +508,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
int index = modulo(i, getWindowSize());
int oldRelativeIndex;
if (mViewsMap.containsKey(index)) {
- oldRelativeIndex = mViewsMap.get(index).index;
+ oldRelativeIndex = mViewsMap.get(index).relativeIndex;
} else {
oldRelativeIndex = -1;
}
@@ -510,14 +521,16 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (inOldRange) {
View view = mViewsMap.get(index).view;
- mViewsMap.get(index).index = newRelativeIndex;
+ mViewsMap.get(index).relativeIndex = newRelativeIndex;
applyTransformForChildAtIndex(view, newRelativeIndex);
transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate);
// Otherwise this view is new to the window
} else {
// Get the new view from the adapter, add it and apply any transform / animation
- View newView = mAdapter.getView(modulo(i, adapterCount), null, this);
+ final int adapterPosition = modulo(i, adapterCount);
+ View newView = mAdapter.getView(adapterPosition, null, this);
+ long itemId = mAdapter.getItemId(adapterPosition);
// We wrap the new view in a FrameLayout so as to respect the contract
// with the adapter, that is, that we don't modify this view directly
@@ -527,7 +540,8 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (newView != null) {
fl.addView(newView);
}
- mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex));
+ mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex,
+ adapterPosition, itemId));
addChild(fl);
applyTransformForChildAtIndex(fl, newRelativeIndex);
transformViewForTransition(-1, newRelativeIndex, fl, animate);
@@ -604,6 +618,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
case MotionEvent.ACTION_UP: {
if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
final View v = getCurrentView();
+ final ViewAndMetaData viewData = getMetaDataForChild(v);
if (v != null) {
if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
final Handler handler = getHandler();
@@ -616,7 +631,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
hideTapFeedback(v);
post(new Runnable() {
public void run() {
- performItemClick(v, 0, 0);
+ if (viewData != null) {
+ performItemClick(v, viewData.adapterPosition,
+ viewData.itemId);
+ } else {
+ performItemClick(v, 0, 0);
+ }
}
});
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index bf63607..8d4aaea 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -199,11 +199,8 @@ public class CheckedTextView extends TextView implements Checkable {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
- if (!populated) {
- event.setChecked(mChecked);
- }
- return populated;
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setChecked(mChecked);
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0df45cc..a730018 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -208,22 +208,9 @@ public abstract class CompoundButton extends Button implements Checkable {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
-
- if (!populated) {
- int resourceId = 0;
- if (mChecked) {
- resourceId = R.string.accessibility_compound_button_selected;
- } else {
- resourceId = R.string.accessibility_compound_button_unselected;
- }
- String state = getResources().getString(resourceId);
- event.getText().add(state);
- event.setChecked(mChecked);
- }
-
- return populated;
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setChecked(mChecked);
}
@Override
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 516162a..6c4c39d 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -21,7 +21,6 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
-import android.util.Config;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -440,7 +439,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
*/
protected void onContentChanged() {
if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
+ if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
mDataValid = mCursor.requery();
}
}
diff --git a/core/java/android/widget/CursorTreeAdapter.java b/core/java/android/widget/CursorTreeAdapter.java
index 3fadf4c..44d1656 100644
--- a/core/java/android/widget/CursorTreeAdapter.java
+++ b/core/java/android/widget/CursorTreeAdapter.java
@@ -22,7 +22,6 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
-import android.util.Config;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
@@ -499,7 +498,7 @@ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implem
@Override
public void onChange(boolean selfChange) {
if (mAutoRequery && mCursor != null) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor +
+ if (false) Log.v("Cursor", "Auto requerying " + mCursor +
" due to update");
mDataValid = mCursor.requery();
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 1d442db..30fb927 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -353,13 +353,14 @@ public class DatePicker extends FrameLayout {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
| DateUtils.FORMAT_SHOW_YEAR;
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mCurrentDate.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
- return true;
}
/**
@@ -410,74 +411,28 @@ public class DatePicker extends FrameLayout {
}
/**
- * Reorders the spinners according to the date format in the current
- * {@link Locale}.
+ * Reorders the spinners according to the date format that is
+ * explicitly set by the user and if no such is set fall back
+ * to the current locale's default format.
*/
private void reorderSpinners() {
- java.text.DateFormat format;
- String order;
-
- /*
- * If the user is in a locale where the medium date format is still
- * numeric (Japanese and Czech, for example), respect the date format
- * order setting. Otherwise, use the order that the locale says is
- * appropriate for a spelled-out date.
- */
-
- if (getShortMonths()[0].startsWith("1")) {
- format = DateFormat.getDateFormat(getContext());
- } else {
- format = DateFormat.getMediumDateFormat(getContext());
- }
-
- if (format instanceof SimpleDateFormat) {
- order = ((SimpleDateFormat) format).toPattern();
- } else {
- // Shouldn't happen, but just in case.
- order = new String(DateFormat.getDateFormatOrder(getContext()));
- }
-
- /*
- * Remove the 3 spinners from their parent and then add them back in the
- * required order.
- */
- LinearLayout parent = mSpinners;
- parent.removeAllViews();
-
- boolean quoted = false;
- boolean didDay = false, didMonth = false, didYear = false;
-
- for (int i = 0; i < order.length(); i++) {
- char c = order.charAt(i);
-
- if (c == '\'') {
- quoted = !quoted;
- }
-
- if (!quoted) {
- if (c == DateFormat.DATE && !didDay) {
- parent.addView(mDaySpinner);
- didDay = true;
- } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) {
- parent.addView(mMonthSpinner);
- didMonth = true;
- } else if (c == DateFormat.YEAR && !didYear) {
- parent.addView(mYearSpinner);
- didYear = true;
- }
+ mSpinners.removeAllViews();
+ char[] order = DateFormat.getDateFormatOrder(getContext());
+ for (int i = 0; i < order.length; i++) {
+ switch (order[i]) {
+ case DateFormat.DATE:
+ mSpinners.addView(mDaySpinner);
+ break;
+ case DateFormat.MONTH:
+ mSpinners.addView(mMonthSpinner);
+ break;
+ case DateFormat.YEAR:
+ mSpinners.addView(mYearSpinner);
+ break;
+ default:
+ throw new IllegalArgumentException();
}
}
-
- // Shouldn't happen, but just in case.
- if (!didMonth) {
- parent.addView(mMonthSpinner);
- }
- if (!didDay) {
- parent.addView(mDaySpinner);
- }
- if (!didYear) {
- parent.addView(mYearSpinner);
- }
}
/**
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index f862368..ead9b4f 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -599,12 +599,35 @@ public class ExpandableListView extends ListView {
* was already expanded, this will return false)
*/
public boolean expandGroup(int groupPos) {
- boolean retValue = mConnector.expandGroup(groupPos);
+ return expandGroup(groupPos, false);
+ }
+
+ /**
+ * Expand a group in the grouped list view
+ *
+ * @param groupPos the group to be expanded
+ * @param animate true if the expanding group should be animated in
+ * @return True if the group was expanded, false otherwise (if the group
+ * was already expanded, this will return false)
+ */
+ public boolean expandGroup(int groupPos, boolean animate) {
+ PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1));
+ boolean retValue = mConnector.expandGroup(pm);
if (mOnGroupExpandListener != null) {
mOnGroupExpandListener.onGroupExpand(groupPos);
}
-
+
+ if (animate) {
+ final int groupFlatPos = pm.position.flatListPos;
+
+ final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
+ smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
+ shiftedGroupPosition);
+ }
+ pm.recycle();
+
return retValue;
}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index f659ead..6b498fe 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -16,6 +16,8 @@
package android.widget;
+import java.util.ArrayList;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -23,14 +25,12 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.Gravity;
import android.widget.RemoteViews.RemoteView;
-import java.util.ArrayList;
-
/**
* FrameLayout is designed to block out an area on the screen to display
@@ -39,7 +39,7 @@ import java.util.ArrayList;
* Children are drawn in a stack, with the most recently added child on top.
* The size of the frame layout is the size of its largest child (plus padding), visible
* or not (if the FrameLayout's parent permits). Views that are GONE are used for sizing
- * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()}
+ * only if {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}
* is set to true.
*
* @attr ref android.R.styleable#FrameLayout_foreground
@@ -115,7 +115,7 @@ public class FrameLayout extends ViewGroup {
}
/**
- * Describes how the foreground is positioned. Defaults to FILL.
+ * Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link android.view.Gravity}
*
@@ -124,8 +124,8 @@ public class FrameLayout extends ViewGroup {
@android.view.RemotableViewMethod
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
- if ((foregroundGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
- foregroundGravity |= Gravity.LEFT;
+ if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+ foregroundGravity |= Gravity.START;
}
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
@@ -364,10 +364,10 @@ public class FrameLayout extends ViewGroup {
gravity = DEFAULT_CHILD_GRAVITY;
}
- final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
- switch (horizontalGravity) {
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
childLeft = parentLeft + lp.leftMargin;
break;
@@ -436,7 +436,7 @@ public class FrameLayout extends ViewGroup {
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
- foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
+ foreground.getIntrinsicHeight(), selfBounds, overlayBounds, isLayoutRtl());
foreground.setBounds(overlayBounds);
}
@@ -485,6 +485,11 @@ public class FrameLayout extends ViewGroup {
return new FrameLayout.LayoutParams(getContext(), attrs);
}
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
/**
* {@inheritDoc}
*/
@@ -566,4 +571,3 @@ public class FrameLayout extends ViewGroup {
}
}
}
-
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
new file mode 100644
index 0000000..bda82a3
--- /dev/null
+++ b/core/java/android/widget/GridLayout.java
@@ -0,0 +1,2285 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.internal.R.styleable;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * A layout that places its children in a rectangular <em>grid</em>.
+ * <p>
+ * The grid is composed of a set of infinitely thin lines that separate the
+ * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
+ * by grid <em>indices</em>. A grid with {@code N} columns
+ * has {@code N + 1} grid indices that run from {@code 0}
+ * through {@code N} inclusive. Regardless of how GridLayout is
+ * configured, grid index {@code 0} is fixed to the leading edge of the
+ * container and grid index {@code N} is fixed to its trailing edge
+ * (after padding is taken into account).
+ *
+ * <h4>Row and Column Groups</h4>
+ *
+ * Children occupy one or more contiguous cells, as defined
+ * by their {@link GridLayout.LayoutParams#rowGroup rowGroup} and
+ * {@link GridLayout.LayoutParams#columnGroup columnGroup} layout parameters.
+ * Each group specifies the set of rows or columns that are to be
+ * occupied; and how children should be aligned within the resulting group of cells.
+ * Although cells do not normally overlap in a GridLayout, GridLayout does
+ * not prevent children being defined to occupy the same cell or group of cells.
+ * In this case however, there is no guarantee that children will not themselves
+ * overlap after the layout operation completes.
+ *
+ * <h4>Default Cell Assignment</h4>
+ *
+ * If no child specifies the row and column indices of the cell it
+ * wishes to occupy, GridLayout assigns cell locations automatically using its:
+ * {@link GridLayout#setOrientation(int) orientation},
+ * {@link GridLayout#setRowCount(int) rowCount} and
+ * {@link GridLayout#setColumnCount(int) columnCount} properties.
+ *
+ * <h4>Space</h4>
+ *
+ * Space between children may be specified either by using instances of the
+ * dedicated {@link Space} view or by setting the
+ *
+ * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
+ * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
+ * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
+ * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
+ *
+ * layout parameters. When the
+ * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
+ * property is set, default margins around children are automatically
+ * allocated based on the child's visual characteristics. Each of the
+ * margins so defined may be independently overridden by an assignment
+ * to the appropriate layout parameter.
+ *
+ * <h4>Excess Space Distribution</h4>
+ *
+ * Like {@link LinearLayout}, a child's ability to stretch is controlled
+ * using <em>weights</em>, which are specified using the
+ * {@link GridLayout.LayoutParams#rowWeight rowWeight} and
+ * {@link GridLayout.LayoutParams#columnWeight columnWeight} layout parameters.
+ * <p>
+ * <p>
+ * See {@link GridLayout.LayoutParams} for a full description of the
+ * layout parameters used by GridLayout.
+ *
+ * @attr ref android.R.styleable#GridLayout_orientation
+ * @attr ref android.R.styleable#GridLayout_rowCount
+ * @attr ref android.R.styleable#GridLayout_columnCount
+ * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+ * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+ * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ */
+public class GridLayout extends ViewGroup {
+
+ // Public constants
+
+ /**
+ * The horizontal orientation.
+ */
+ public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+
+ /**
+ * The vertical orientation.
+ */
+ public static final int VERTICAL = LinearLayout.VERTICAL;
+
+ /**
+ * The constant used to indicate that a value is undefined.
+ * Fields can use this value to indicate that their values
+ * have not yet been set. Similarly, methods can return this value
+ * to indicate that there is no suitable value that the implementation
+ * can return.
+ * The value used for the constant (currently {@link Integer#MIN_VALUE}) is
+ * intended to avoid confusion between valid values whose sign may not be known.
+ */
+ public static final int UNDEFINED = Integer.MIN_VALUE;
+
+ // Misc constants
+
+ private static final String TAG = GridLayout.class.getName();
+ private static final boolean DEBUG = false;
+ private static final Paint GRID_PAINT = new Paint();
+ private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
+ private static final int MIN = 0;
+ private static final int PRF = 1;
+ private static final int MAX = 2;
+
+ // Defaults
+
+ private static final int DEFAULT_ORIENTATION = HORIZONTAL;
+ private static final int DEFAULT_COUNT = UNDEFINED;
+ private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
+ private static final boolean DEFAULT_ORDER_PRESERVED = false;
+ private static final boolean DEFAULT_MARGINS_INCLUDED = true;
+ // todo remove this
+ private static final int DEFAULT_CONTAINER_MARGIN = 20;
+
+ // TypedArray indices
+
+ private static final int ORIENTATION = styleable.GridLayout_orientation;
+ private static final int ROW_COUNT = styleable.GridLayout_rowCount;
+ private static final int COLUMN_COUNT = styleable.GridLayout_columnCount;
+ private static final int USE_DEFAULT_MARGINS = styleable.GridLayout_useDefaultMargins;
+ private static final int MARGINS_INCLUDED = styleable.GridLayout_marginsIncludedInAlignment;
+ private static final int ROW_ORDER_PRESERVED = styleable.GridLayout_rowOrderPreserved;
+ private static final int COLUMN_ORDER_PRESERVED = styleable.GridLayout_columnOrderPreserved;
+
+ // Static initialization
+
+ static {
+ GRID_PAINT.setColor(Color.argb(50, 255, 255, 255));
+ }
+
+ // Instance variables
+
+ private final Axis mHorizontalAxis = new Axis(true);
+ private final Axis mVerticalAxis = new Axis(false);
+ private boolean mLayoutParamsValid = false;
+ private int mOrientation = DEFAULT_ORIENTATION;
+ private boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
+ private boolean mMarginsIncludedInAlignment = DEFAULT_MARGINS_INCLUDED;
+ private int mDefaultGravity = Gravity.NO_GRAVITY;
+
+ /* package */ boolean accommodateBothMinAndMax = false;
+
+ // Constructors
+
+ /**
+ * {@inheritDoc}
+ */
+ public GridLayout(Context context) {
+ super(context);
+ if (DEBUG) {
+ setWillNotDraw(false);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public GridLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ processAttributes(context, attrs);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public GridLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ processAttributes(context, attrs);
+ }
+
+ private void processAttributes(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout);
+ try {
+ setRowCount(a.getInteger(ROW_COUNT, DEFAULT_COUNT));
+ setColumnCount(a.getInteger(COLUMN_COUNT, DEFAULT_COUNT));
+ mOrientation = a.getInteger(ORIENTATION, DEFAULT_ORIENTATION);
+ mUseDefaultMargins = a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS);
+ mMarginsIncludedInAlignment = a.getBoolean(MARGINS_INCLUDED, DEFAULT_MARGINS_INCLUDED);
+ setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
+ setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
+ } finally {
+ a.recycle();
+ }
+ }
+
+ // Implementation
+
+ /**
+ * Returns the current orientation.
+ *
+ * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
+ *
+ * @see #setOrientation(int)
+ *
+ * @attr ref android.R.styleable#GridLayout_orientation
+ */
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ /**
+ * The orientation property does not affect layout. Orientation is used
+ * only to generate default row/column indices when they are not specified
+ * by a component's layout parameters.
+ * <p>
+ * The default value of this property is {@link #HORIZONTAL}.
+ *
+ * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
+ *
+ * @see #getOrientation()
+ *
+ * @attr ref android.R.styleable#GridLayout_orientation
+ */
+ public void setOrientation(int orientation) {
+ if (mOrientation != orientation) {
+ mOrientation = orientation;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Returns the current number of rows. This is either the last value that was set
+ * with {@link #setRowCount(int)} or, if no such value was set, the maximum
+ * value of each the upper bounds defined in {@link LayoutParams#rowGroup}.
+ *
+ * @return the current number of rows
+ *
+ * @see #setRowCount(int)
+ * @see LayoutParams#rowGroup
+ *
+ * @attr ref android.R.styleable#GridLayout_rowCount
+ */
+ public int getRowCount() {
+ return mVerticalAxis.getCount();
+ }
+
+ /**
+ * The rowCount property does not affect layout. RowCount is used
+ * only to generate default row/column indices when they are not specified
+ * by a component's layout parameters.
+ *
+ * @param rowCount the number of rows
+ *
+ * @see #getRowCount()
+ * @see LayoutParams#rowGroup
+ *
+ * @attr ref android.R.styleable#GridLayout_rowCount
+ */
+ public void setRowCount(int rowCount) {
+ mVerticalAxis.setCount(rowCount);
+ }
+
+ /**
+ * Returns the current number of columns. This is either the last value that was set
+ * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
+ * value of each the upper bounds defined in {@link LayoutParams#columnGroup}.
+ *
+ * @return the current number of columns
+ *
+ * @see #setColumnCount(int)
+ * @see LayoutParams#columnGroup
+ *
+ * @attr ref android.R.styleable#GridLayout_columnCount
+ */
+ public int getColumnCount() {
+ return mHorizontalAxis.getCount();
+ }
+
+ /**
+ * The columnCount property does not affect layout. ColumnCount is used
+ * only to generate default column/column indices when they are not specified
+ * by a component's layout parameters.
+ *
+ * @param columnCount the number of columns.
+ *
+ * @see #getColumnCount()
+ * @see LayoutParams#columnGroup
+ *
+ * @attr ref android.R.styleable#GridLayout_columnCount
+ */
+ public void setColumnCount(int columnCount) {
+ mHorizontalAxis.setCount(columnCount);
+ }
+
+ /**
+ * Returns whether or not this GridLayout will allocate default margins when no
+ * corresponding layout parameters are defined.
+ *
+ * @return {@code true} if default margins should be allocated
+ *
+ * @see #setUseDefaultMargins(boolean)
+ *
+ * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+ */
+ public boolean getUseDefaultMargins() {
+ return mUseDefaultMargins;
+ }
+
+ /**
+ * When {@code true}, GridLayout allocates default margins around children
+ * based on the child's visual characteristics. Each of the
+ * margins so defined may be independently overridden by an assignment
+ * to the appropriate layout parameter.
+ * <p>
+ * When {@code false}, the default value of all margins is zero.
+ * <p>
+ * When setting to {@code true}, consider setting the value of the
+ * {@link #setMarginsIncludedInAlignment(boolean) marginsIncludedInAlignment}
+ * property to {@code false}.
+ * <p>
+ * The default value of this property is {@code false}.
+ *
+ * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
+ *
+ * @see #getUseDefaultMargins()
+ * @see #setMarginsIncludedInAlignment(boolean)
+ *
+ * @see MarginLayoutParams#leftMargin
+ * @see MarginLayoutParams#topMargin
+ * @see MarginLayoutParams#rightMargin
+ * @see MarginLayoutParams#bottomMargin
+ *
+ * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+ */
+ public void setUseDefaultMargins(boolean useDefaultMargins) {
+ mUseDefaultMargins = useDefaultMargins;
+ requestLayout();
+ }
+
+ /**
+ * Returns whether GridLayout aligns the edges of the view or the edges
+ * of the larger rectangle created by extending the view by its associated
+ * margins.
+ *
+ * @see #setMarginsIncludedInAlignment(boolean)
+ *
+ * @return {@code true} if alignment is between edges including margins
+ *
+ * @attr ref android.R.styleable#GridLayout_marginsIncludedInAlignment
+ */
+ public boolean getMarginsIncludedInAlignment() {
+ return mMarginsIncludedInAlignment;
+ }
+
+ /**
+ * When {@code true}, the bounds of a view are extended outwards according to its
+ * margins before the edges of the resulting rectangle are aligned.
+ * When {@code false}, alignment occurs between the bounds of the view - i.e.
+ * {@link #LEFT} alignment means align the left edges of the view.
+ * <p>
+ * The default value of this property is {@code true}.
+ *
+ * @param marginsIncludedInAlignment {@code true} if alignment between edges includes margins
+ *
+ * @see #getMarginsIncludedInAlignment()
+ *
+ * @attr ref android.R.styleable#GridLayout_marginsIncludedInAlignment
+ */
+ public void setMarginsIncludedInAlignment(boolean marginsIncludedInAlignment) {
+ mMarginsIncludedInAlignment = marginsIncludedInAlignment;
+ requestLayout();
+ }
+
+ /**
+ * Returns whether or not row boundaries are ordered by their grid indices.
+ *
+ * @return {@code true} if row boundaries must appear in the order of their indices,
+ * {@code false} otherwise
+ *
+ * @see #setRowOrderPreserved(boolean)
+ *
+ * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+ */
+ public boolean isRowOrderPreserved() {
+ return mVerticalAxis.isOrderPreserved();
+ }
+
+ /**
+ * When this property is {@code false}, the default state, GridLayout
+ * is at liberty to choose an order that better suits the heights of its children.
+ <p>
+ * When this property is {@code true}, GridLayout is forced to place the row boundaries
+ * so that their associated grid indices are in ascending order in the view.
+ * <p>
+ * GridLayout implements this specification by creating ordering constraints between
+ * the variables that represent the locations of the row boundaries.
+ *
+ * When this property is {@code true}, constraints are added for each pair of consecutive
+ * indices: i.e. between row boundaries: {@code [0..1], [1..2], [2..3],...} etc.
+ *
+ * When the property is {@code false}, the ordering constraints are placed
+ * only between boundaries that separate opposing edges of the layout's children.
+ * <p>
+ * The default value of this property is {@code false}.
+
+ * @param rowOrderPreserved {@code true} to force GridLayout to respect the order
+ * of row boundaries
+ *
+ * @see #isRowOrderPreserved()
+ *
+ * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+ */
+ public void setRowOrderPreserved(boolean rowOrderPreserved) {
+ mVerticalAxis.setOrderPreserved(rowOrderPreserved);
+ invalidateStructure();
+ requestLayout();
+ }
+
+ /**
+ * Returns whether or not column boundaries are ordered by their grid indices.
+ *
+ * @return {@code true} if column boundaries must appear in the order of their indices,
+ * {@code false} otherwise
+ *
+ * @see #setColumnOrderPreserved(boolean)
+ *
+ * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ */
+ public boolean isColumnOrderPreserved() {
+ return mHorizontalAxis.isOrderPreserved();
+ }
+
+ /**
+ * When this property is {@code false}, the default state, GridLayout
+ * is at liberty to choose an order that better suits the widths of its children.
+ <p>
+ * When this property is {@code true}, GridLayout is forced to place the column boundaries
+ * so that their associated grid indices are in ascending order in the view.
+ * <p>
+ * GridLayout implements this specification by creating ordering constraints between
+ * the variables that represent the locations of the column boundaries.
+ *
+ * When this property is {@code true}, constraints are added for each pair of consecutive
+ * indices: i.e. between column boundaries: {@code [0..1], [1..2], [2..3],...} etc.
+ *
+ * When the property is {@code false}, the ordering constraints are placed
+ * only between boundaries that separate opposing edges of the layout's children.
+ * <p>
+ * The default value of this property is {@code false}.
+ *
+ * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
+ * of column boundaries.
+ *
+ * @see #isColumnOrderPreserved()
+ *
+ * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ */
+ public void setColumnOrderPreserved(boolean columnOrderPreserved) {
+ mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
+ invalidateStructure();
+ requestLayout();
+ }
+
+ private static int sum(float[] a) {
+ int result = 0;
+ for (int i = 0, length = a.length; i < length; i++) {
+ result += a[i];
+ }
+ return result;
+ }
+
+ private int getDefaultMargin(View c, boolean leading, boolean horizontal) {
+ // In the absence of any other information, calculate a default gap such
+ // that, in a grid of identical components, the heights and the vertical
+ // gaps are in the proportion of the golden ratio.
+ // To effect this with equal margins at each edge, set each of the
+ // four margin values to half this amount.
+ return (int) (c.getMeasuredHeight() / GOLDEN_RATIO / 2);
+ }
+
+ private int getDefaultMargin(View c, boolean isAtEdge, boolean leading, boolean horizontal) {
+ // todo remove DEFAULT_CONTAINER_MARGIN. Use padding? Seek advice on Themes/Styles, etc.
+ return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, leading, horizontal);
+ }
+
+ private int getDefaultMarginValue(View c, LayoutParams p, boolean leading, boolean horizontal) {
+ if (!mUseDefaultMargins) {
+ return 0;
+ }
+ Group group = horizontal ? p.columnGroup : p.rowGroup;
+ Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
+ Interval span = group.span;
+ boolean isAtEdge = leading ? (span.min == 0) : (span.max == axis.getCount());
+
+ return getDefaultMargin(c, isAtEdge, leading, horizontal);
+ }
+
+ private int getMargin(View view, boolean leading, boolean horizontal) {
+ LayoutParams lp = getLayoutParams(view);
+ int margin = horizontal ?
+ (leading ? lp.leftMargin : lp.rightMargin) :
+ (leading ? lp.topMargin : lp.bottomMargin);
+ return margin == UNDEFINED ? getDefaultMarginValue(view, lp, leading, horizontal) : margin;
+ }
+
+ private static int valueIfDefined(int value, int defaultValue) {
+ return (value != UNDEFINED) ? value : defaultValue;
+ }
+
+ // install default indices for cells that don't define them
+ private void validateLayoutParams() {
+ new Object() {
+ public int maxSize = 0;
+
+ private int valueIfDefined2(int value, int defaultValue) {
+ if (value != UNDEFINED) {
+ maxSize = 0;
+ return value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ {
+ final boolean horizontal = (mOrientation == HORIZONTAL);
+ final int axis = horizontal ? mHorizontalAxis.count : mVerticalAxis.count;
+ final int count = valueIfDefined(axis, Integer.MAX_VALUE);
+
+ int row = 0;
+ int col = 0;
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ LayoutParams lp = getLayoutParams1(getChildAt(i));
+
+ Group colGroup = lp.columnGroup;
+ Interval cols = colGroup.span;
+ int colSpan = cols.size();
+
+ Group rowGroup = lp.rowGroup;
+ Interval rows = rowGroup.span;
+ int rowSpan = rows.size();
+
+ if (horizontal) {
+ row = valueIfDefined2(rows.min, row);
+
+ int newCol = valueIfDefined(cols.min, (col + colSpan > count) ? 0 : col);
+ if (newCol < col) {
+ row += maxSize;
+ maxSize = 0;
+ }
+ col = newCol;
+ maxSize = max(maxSize, rowSpan);
+ } else {
+ col = valueIfDefined2(cols.min, col);
+
+ int newRow = valueIfDefined(rows.min, (row + rowSpan > count) ? 0 : row);
+ if (newRow < row) {
+ col += maxSize;
+ maxSize = 0;
+ }
+ row = newRow;
+ maxSize = max(maxSize, colSpan);
+ }
+
+ lp.setColumnGroupSpan(new Interval(col, col + colSpan));
+ lp.setRowGroupSpan(new Interval(row, row + rowSpan));
+
+ if (horizontal) {
+ col = col + colSpan;
+ } else {
+ row = row + rowSpan;
+ }
+ }
+ }
+ };
+ invalidateStructure();
+ }
+
+ private void invalidateStructure() {
+ mLayoutParamsValid = false;
+ mHorizontalAxis.invalidateStructure();
+ mVerticalAxis.invalidateStructure();
+ // This can end up being done twice. But better that than not at all.
+ invalidateValues();
+ }
+
+ private void invalidateValues() {
+ // Need null check because requestLayout() is called in View's initializer,
+ // before we are set up.
+ if (mHorizontalAxis != null && mVerticalAxis != null) {
+ mHorizontalAxis.invalidateValues();
+ mVerticalAxis.invalidateValues();
+ }
+ }
+
+ private LayoutParams getLayoutParams1(View c) {
+ return (LayoutParams) c.getLayoutParams();
+ }
+
+ private LayoutParams getLayoutParams(View c) {
+ if (!mLayoutParamsValid) {
+ validateLayoutParams();
+ mLayoutParamsValid = true;
+ }
+ return getLayoutParams1(c);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs, mDefaultGravity);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+ // Draw grid
+
+ private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
+ int dx = getPaddingLeft();
+ int dy = getPaddingTop();
+ graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (DEBUG) {
+ int height = getHeight() - getPaddingTop() - getPaddingBottom();
+ int width = getWidth() - getPaddingLeft() - getPaddingRight();
+
+ int[] xs = mHorizontalAxis.locations;
+ for (int i = 0, length = xs.length; i < length; i++) {
+ int x = xs[i];
+ drawLine(canvas, x, 0, x, height - 1, GRID_PAINT);
+ }
+ int[] ys = mVerticalAxis.locations;
+ for (int i = 0, length = ys.length; i < length; i++) {
+ int y = ys[i];
+ drawLine(canvas, 0, y, width - 1, y, GRID_PAINT);
+ }
+ }
+ }
+
+ // Add/remove
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ super.addView(child, index, params);
+ invalidateStructure();
+ }
+
+ @Override
+ public void removeView(View view) {
+ super.removeView(view);
+ invalidateStructure();
+ }
+
+ @Override
+ public void removeViewInLayout(View view) {
+ super.removeViewInLayout(view);
+ invalidateStructure();
+ }
+
+ @Override
+ public void removeViewsInLayout(int start, int count) {
+ super.removeViewsInLayout(start, count);
+ invalidateStructure();
+ }
+
+ @Override
+ public void removeViewAt(int index) {
+ super.removeViewAt(index);
+ invalidateStructure();
+ }
+
+ // Measurement
+
+ private static int getChildMeasureSpec2(int spec, int padding, int childDimension) {
+ int resultSize;
+ int resultMode;
+
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = EXACTLY;
+ } else {
+ /*
+ using the following lines would replicate the logic of ViewGroup.getChildMeasureSpec()
+
+ int specMode = MeasureSpec.getMode(spec);
+ int specSize = MeasureSpec.getSize(spec);
+ int size = Math.max(0, specSize - padding);
+
+ resultSize = size;
+ resultMode = (specMode == EXACTLY && childDimension == LayoutParams.WRAP_CONTENT) ?
+ AT_MOST : specMode;
+ */
+ resultSize = 0;
+ resultMode = UNSPECIFIED;
+ }
+ return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+ }
+
+ @Override
+ protected void measureChild(View child, int parentWidthSpec, int parentHeightSpec) {
+ ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+ int childWidthMeasureSpec = getChildMeasureSpec2(parentWidthSpec,
+ mPaddingLeft + mPaddingRight, lp.width);
+ int childHeightMeasureSpec = getChildMeasureSpec2(parentHeightSpec,
+ mPaddingTop + mPaddingBottom, lp.height);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ measureChildren(widthSpec, heightSpec);
+
+ int computedWidth = getPaddingLeft() + mHorizontalAxis.getMin() + getPaddingRight();
+ int computedHeight = getPaddingTop() + mVerticalAxis.getMin() + getPaddingBottom();
+
+ setMeasuredDimension(
+ resolveSizeAndState(computedWidth, widthSpec, 0),
+ resolveSizeAndState(computedHeight, heightSpec, 0));
+ }
+
+ private int protect(int alignment) {
+ return (alignment == UNDEFINED) ? 0 : alignment;
+ }
+
+ private int getMeasurement(View c, boolean horizontal, int measurementType) {
+ return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
+ }
+
+ private int getMeasurementIncludingMargin(View c, boolean horizontal, int measurementType) {
+ int result = getMeasurement(c, horizontal, measurementType);
+ if (mMarginsIncludedInAlignment) {
+ int leadingMargin = getMargin(c, true, horizontal);
+ int trailingMargin = getMargin(c, false, horizontal);
+ return result + leadingMargin + trailingMargin;
+ }
+ return result;
+ }
+
+ @Override
+ public void requestLayout() {
+ super.requestLayout();
+ invalidateValues();
+ }
+
+ // Layout container
+
+ /**
+ * {@inheritDoc}
+ */
+ /*
+ The layout operation is implemented by delegating the heavy lifting to the
+ to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class.
+ Together they compute the locations of the vertical and horizontal lines of
+ the grid (respectively!).
+
+ This method is then left with the simpler task of applying margins, gravity
+ and sizing to each child view and then placing it in its cell.
+ */
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int targetWidth = r - l;
+ int targetHeight = b - t;
+
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+ int paddingRight = getPaddingRight();
+ int paddingBottom = getPaddingBottom();
+
+ mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
+ mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
+
+ for (int i = 0, size = getChildCount(); i < size; i++) {
+ View view = getChildAt(i);
+ LayoutParams lp = getLayoutParams(view);
+ Group columnGroup = lp.columnGroup;
+ Group rowGroup = lp.rowGroup;
+
+ Interval colSpan = columnGroup.span;
+ Interval rowSpan = rowGroup.span;
+
+ int x1 = mHorizontalAxis.getLocationIncludingMargin(view, true, colSpan.min);
+ int y1 = mVerticalAxis.getLocationIncludingMargin(view, true, rowSpan.min);
+
+ int x2 = mHorizontalAxis.getLocationIncludingMargin(view, false, colSpan.max);
+ int y2 = mVerticalAxis.getLocationIncludingMargin(view, false, rowSpan.max);
+
+ int cellWidth = x2 - x1;
+ int cellHeight = y2 - y1;
+
+ int pWidth = getMeasurement(view, true, PRF);
+ int pHeight = getMeasurement(view, false, PRF);
+
+ Alignment hAlignment = columnGroup.alignment;
+ Alignment vAlignment = rowGroup.alignment;
+
+ int dx, dy;
+
+ Bounds colBounds = mHorizontalAxis.getGroupBounds().getValue(i);
+ Bounds rowBounds = mVerticalAxis.getGroupBounds().getValue(i);
+
+ // Gravity offsets: the location of the alignment group relative to its cell group.
+ int c2ax = protect(hAlignment.getAlignmentValue(null, cellWidth - colBounds.size()));
+ int c2ay = protect(vAlignment.getAlignmentValue(null, cellHeight - rowBounds.size()));
+
+ if (mMarginsIncludedInAlignment) {
+ int leftMargin = getMargin(view, true, true);
+ int topMargin = getMargin(view, true, false);
+ int rightMargin = getMargin(view, false, true);
+ int bottomMargin = getMargin(view, false, false);
+
+ // Same calculation as getMeasurementIncludingMargin()
+ int measuredWidth = leftMargin + pWidth + rightMargin;
+ int measuredHeight = topMargin + pHeight + bottomMargin;
+
+ // Alignment offsets: the location of the view relative to its alignment group.
+ int a2vx = colBounds.before - hAlignment.getAlignmentValue(view, measuredWidth);
+ int a2vy = rowBounds.before - vAlignment.getAlignmentValue(view, measuredHeight);
+
+ dx = c2ax + a2vx + leftMargin;
+ dy = c2ay + a2vy + topMargin;
+
+ cellWidth -= leftMargin + rightMargin;
+ cellHeight -= topMargin + bottomMargin;
+ } else {
+ // Alignment offsets: the location of the view relative to its alignment group.
+ int a2vx = colBounds.before - hAlignment.getAlignmentValue(view, pWidth);
+ int a2vy = rowBounds.before - vAlignment.getAlignmentValue(view, pHeight);
+
+ dx = c2ax + a2vx;
+ dy = c2ay + a2vy;
+ }
+
+ int width = hAlignment.getSizeInCell(view, pWidth, cellWidth);
+ int height = vAlignment.getSizeInCell(view, pHeight, cellHeight);
+
+ int cx = paddingLeft + x1 + dx;
+ int cy = paddingTop + y1 + dy;
+ view.layout(cx, cy, cx + width, cy + height);
+ }
+ }
+
+ // Inner classes
+
+ /*
+ This internal class houses the algorithm for computing the locations of grid lines;
+ along either the horizontal or vertical axis. A GridLayout uses two instances of this class -
+ distinguished by the "horizontal" flag which is true for the horizontal axis and false
+ for the vertical one.
+ */
+ private class Axis {
+ private static final int MIN_VALUE = -1000000;
+
+ private static final int UNVISITED = 0;
+ private static final int PENDING = 1;
+ private static final int COMPLETE = 2;
+
+ public final boolean horizontal;
+
+ public int count = UNDEFINED;
+ public boolean countValid = false;
+ public boolean countWasExplicitySet = false;
+
+ PackedMap<Group, Bounds> groupBounds;
+ public boolean groupBoundsValid = false;
+
+ PackedMap<Interval, MutableInt> spanSizes;
+ public boolean spanSizesValid = false;
+
+ public int[] leadingMargins;
+ public boolean leadingMarginsValid = false;
+
+ public int[] trailingMargins;
+ public boolean trailingMarginsValid = false;
+
+ public Arc[] arcs;
+ public boolean arcsValid = false;
+
+ public int[] minima;
+ public boolean minimaValid = false;
+
+ public float[] weights;
+ public int[] locations;
+
+ private boolean mOrderPreserved = DEFAULT_ORDER_PRESERVED;
+
+ private Axis(boolean horizontal) {
+ this.horizontal = horizontal;
+ }
+
+ private int maxIndex() {
+ // note the number Integer.MIN_VALUE + 1 comes up in undefined cells
+ int count = -1;
+ for (int i = 0, size = getChildCount(); i < size; i++) {
+ LayoutParams params = getLayoutParams(getChildAt(i));
+ Group g = horizontal ? params.columnGroup : params.rowGroup;
+ count = max(count, g.span.min);
+ count = max(count, g.span.max);
+ }
+ return count == -1 ? UNDEFINED : count;
+ }
+
+ public int getCount() {
+ if (!countValid) {
+ count = max(0, maxIndex()); // if there are no cells, the count is zero
+ countValid = true;
+ }
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ this.countWasExplicitySet = count != UNDEFINED;
+ }
+
+ public boolean isOrderPreserved() {
+ return mOrderPreserved;
+ }
+
+ public void setOrderPreserved(boolean orderPreserved) {
+ mOrderPreserved = orderPreserved;
+ invalidateStructure();
+ }
+
+ private PackedMap<Group, Bounds> createGroupBounds() {
+ int N = getChildCount();
+ Group[] groups = new Group[N];
+ Bounds[] bounds = new Bounds[N];
+ for (int i = 0; i < N; i++) {
+ LayoutParams lp = getLayoutParams(getChildAt(i));
+ Group group = horizontal ? lp.columnGroup : lp.rowGroup;
+
+ groups[i] = group;
+ bounds[i] = new Bounds();
+ }
+
+ return new PackedMap<Group, Bounds>(groups, bounds);
+ }
+
+ private void computeGroupBounds() {
+ for (int i = 0; i < groupBounds.values.length; i++) {
+ groupBounds.values[i].reset();
+ }
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ View c = getChildAt(i);
+ LayoutParams lp = getLayoutParams(c);
+ Group g = horizontal ? lp.columnGroup : lp.rowGroup;
+
+ Bounds bounds = groupBounds.getValue(i);
+
+ int size = getMeasurementIncludingMargin(c, horizontal, PRF);
+ // todo test this works correctly when the returned value is UNDEFINED
+ int before = g.alignment.getAlignmentValue(c, size);
+ bounds.include(before, size - before);
+ }
+ }
+
+ private PackedMap<Group, Bounds> getGroupBounds() {
+ if (groupBounds == null) {
+ groupBounds = createGroupBounds();
+ }
+ if (!groupBoundsValid) {
+ computeGroupBounds();
+ groupBoundsValid = true;
+ }
+ return groupBounds;
+ }
+
+ // Add values computed by alignment - taking the max of all alignments in each span
+ private PackedMap<Interval, MutableInt> createSpanSizes() {
+ PackedMap<Group, Bounds> groupBounds = getGroupBounds();
+ int N = groupBounds.keys.length;
+ Interval[] spans = new Interval[N];
+ MutableInt[] values = new MutableInt[N];
+ for (int i = 0; i < N; i++) {
+ Interval key = groupBounds.keys[i].span;
+
+ spans[i] = key;
+ values[i] = new MutableInt();
+ }
+ return new PackedMap<Interval, MutableInt>(spans, values);
+ }
+
+ private void computeSpanSizes() {
+ MutableInt[] spans = spanSizes.values;
+ for (int i = 0; i < spans.length; i++) {
+ spans[i].reset();
+ }
+
+ Bounds[] bounds = getGroupBounds().values; // use getter to trigger a re-evaluation
+ for (int i = 0; i < bounds.length; i++) {
+ int value = bounds[i].size();
+
+ MutableInt valueHolder = spanSizes.getValue(i);
+ valueHolder.value = max(valueHolder.value, value);
+ }
+ }
+
+ private PackedMap<Interval, MutableInt> getSpanSizes() {
+ if (spanSizes == null) {
+ spanSizes = createSpanSizes();
+ }
+ if (!spanSizesValid) {
+ computeSpanSizes();
+ spanSizesValid = true;
+ }
+ return spanSizes;
+ }
+
+ private void include(List<Arc> arcs, Interval key, MutableInt size) {
+ // this bit below should really be computed outside here -
+ // its just to stop default (col>0) constraints obliterating valid entries
+ for (Arc arc : arcs) {
+ Interval span = arc.span;
+ if (span.equals(key)) {
+ return;
+ }
+ }
+ arcs.add(new Arc(key, size));
+ }
+
+ private void include2(List<Arc> arcs, Interval span, MutableInt min, MutableInt max,
+ boolean both) {
+ include(arcs, span, min);
+ if (both) {
+ // todo
+// include(arcs, span.inverse(), max.neg());
+ }
+ }
+
+ private void include2(List<Arc> arcs, Interval span, int min, int max, boolean both) {
+ include2(arcs, span, new MutableInt(min), new MutableInt(max), both);
+ }
+
+ // Group arcs by their first vertex, returning an array of arrays.
+ // This is linear in the number of arcs.
+ private Arc[][] groupArcsByFirstVertex(Arc[] arcs) {
+ int N = getCount() + 1;// the number of vertices
+ Arc[][] result = new Arc[N][];
+ int[] sizes = new int[N];
+ for (Arc arc : arcs) {
+ sizes[arc.span.min]++;
+ }
+ for (int i = 0; i < sizes.length; i++) {
+ result[i] = new Arc[sizes[i]];
+ }
+ // reuse the sizes array to hold the current last elements as we insert each arc
+ Arrays.fill(sizes, 0);
+ for (Arc arc : arcs) {
+ int i = arc.span.min;
+ result[i][sizes[i]++] = arc;
+ }
+
+ return result;
+ }
+
+ /*
+ Topological sort.
+ */
+ private Arc[] topologicalSort(final Arc[] arcs, int start) {
+ // todo ensure the <start> vertex is added in edge cases
+ final List<Arc> result = new ArrayList<Arc>();
+ new Object() {
+ Arc[][] arcsByFirstVertex = groupArcsByFirstVertex(arcs);
+ int[] visited = new int[getCount() + 1];
+
+ boolean completesCycle(int loc) {
+ int state = visited[loc];
+ if (state == UNVISITED) {
+ visited[loc] = PENDING;
+ for (Arc arc : arcsByFirstVertex[loc]) {
+ Interval span = arc.span;
+ // the recursive call
+ if (completesCycle(span.max)) {
+ // which arcs get set here is dependent on the order
+ // in which we explore nodes
+ arc.completesCycle = true;
+ }
+ result.add(arc);
+ }
+ visited[loc] = COMPLETE;
+ } else if (state == PENDING) {
+ return true;
+ } else if (state == COMPLETE) {
+ }
+ return false;
+ }
+ }.completesCycle(start);
+ Collections.reverse(result);
+ assert arcs.length == result.size();
+ return result.toArray(new Arc[result.size()]);
+ }
+
+ private boolean[] findUsed(Collection<Arc> arcs) {
+ boolean[] result = new boolean[getCount()];
+ for (Arc arc : arcs) {
+ Interval span = arc.span;
+ int min = min(span.min, span.max);
+ int max = max(span.min, span.max);
+ for (int i = min; i < max; i++) {
+ result[i] = true;
+ }
+ }
+ return result;
+ }
+
+ // todo unify with findUsed above. Both routines analyze which rows/columns are empty.
+ private Collection<Interval> getSpacers() {
+ List<Interval> result = new ArrayList<Interval>();
+ int N = getCount() + 1;
+ int[] leadingEdgeCount = new int[N];
+ int[] trailingEdgeCount = new int[N];
+ for (int i = 0, size = getChildCount(); i < size; i++) {
+ LayoutParams lp = getLayoutParams(getChildAt(i));
+ Group g = horizontal ? lp.columnGroup : lp.rowGroup;
+ Interval span = g.span;
+ leadingEdgeCount[span.min]++;
+ trailingEdgeCount[span.max]++;
+ }
+
+ int lastTrailingEdge = 0;
+
+ // treat the parent's edges like peer edges of the opposite type
+ trailingEdgeCount[0] = 1;
+ leadingEdgeCount[N - 1] = 1;
+
+ for (int i = 0; i < N; i++) {
+ if (trailingEdgeCount[i] > 0) {
+ lastTrailingEdge = i;
+ continue; // if this is also a leading edge, don't add a space of length zero
+ }
+ if (leadingEdgeCount[i] > 0) {
+ result.add(new Interval(lastTrailingEdge, i));
+ }
+ }
+ return result;
+ }
+
+ private Arc[] createArcs() {
+ List<Arc> spanToSize = new ArrayList<Arc>();
+
+ // Add all the preferred elements that were not defined by the user.
+ PackedMap<Interval, MutableInt> spanSizes = getSpanSizes();
+ for (int i = 0; i < spanSizes.keys.length; i++) {
+ Interval key = spanSizes.keys[i];
+ MutableInt value = spanSizes.values[i];
+ // todo remove value duplicate
+ include2(spanToSize, key, value, value, accommodateBothMinAndMax);
+ }
+
+ // Find redundant rows/cols and glue them together with 0-length arcs to link the tree
+ boolean[] used = findUsed(spanToSize);
+ for (int i = 0; i < getCount(); i++) {
+ if (!used[i]) {
+ Interval span = new Interval(i, i + 1);
+ include(spanToSize, span, new MutableInt(0));
+ include(spanToSize, span.inverse(), new MutableInt(0));
+ }
+ }
+
+ if (mOrderPreserved) {
+ // Add preferred gaps
+ for (int i = 0; i < getCount(); i++) {
+ if (used[i]) {
+ include2(spanToSize, new Interval(i, i + 1), 0, 0, false);
+ }
+ }
+ } else {
+ for (Interval gap : getSpacers()) {
+ include2(spanToSize, gap, 0, 0, false);
+ }
+ }
+ Arc[] arcs = spanToSize.toArray(new Arc[spanToSize.size()]);
+ return topologicalSort(arcs, 0);
+ }
+
+ public Arc[] getArcs() {
+ if (arcs == null) {
+ arcs = createArcs();
+ }
+ if (!arcsValid) {
+ getSpanSizes();
+ arcsValid = true;
+ }
+ return arcs;
+ }
+
+ private boolean relax(int[] locations, Arc entry) {
+ Interval span = entry.span;
+ int u = span.min;
+ int v = span.max;
+ int value = entry.value.value;
+ int candidate = locations[u] + value;
+ if (candidate > locations[v]) {
+ locations[v] = candidate;
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N)
+
+ GridLayout converts its requirements into a system of linear constraints of the
+ form:
+
+ x[i] - x[j] < a[k]
+
+ Where the x[i] are variables and the a[k] are constants.
+
+ For example, if the variables were instead labeled x, y, z we might have:
+
+ x - y < 17
+ y - z < 23
+ z - x < 42
+
+ This is a special case of the Linear Programming problem that is, in turn,
+ equivalent to the single-source shortest paths problem on a digraph, for
+ which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
+
+ Other algorithms are faster in the case where no arcs have negative weights
+ but allowing negative weights turns out to be the same as accommodating maximum
+ size requirements as well as minimum ones.
+
+ Bellman-Ford works by iteratively 'relaxing' constraints over all nodes (an O(N)
+ process) and performing this step N times. Proof of correctness hinges on the
+ fact that there can be no negative weight chains of length > N - unless a
+ 'negative weight loop' exists. The algorithm catches this case in a final
+ checking phase that reports failure.
+
+ By topologically sorting the nodes and checking this condition at each step
+ typical layout problems complete after the first iteration and the algorithm
+ completes in O(N) steps with very low constants.
+ */
+ private int[] solve(Arc[] arcs, int[] locations) {
+ int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
+
+ boolean changed = false;
+ // We take one extra pass over traditional Bellman-Ford (and omit their final step)
+ for (int i = 0; i < N; i++) {
+ changed = false;
+ for (int j = 0, length = arcs.length; j < length; j++) {
+ changed = changed | relax(locations, arcs[j]);
+ }
+ if (!changed) {
+ if (DEBUG) {
+ Log.d(TAG, "Iteration " +
+ " completed after " + (1 + i) + " steps out of " + N);
+ }
+ break;
+ }
+ }
+ if (changed) {
+ Log.d(TAG, "*** Algorithm failed to terminate ***");
+ }
+ return locations;
+ }
+
+ private void computeMargins(boolean leading) {
+ int[] margins = leading ? leadingMargins : trailingMargins;
+ for (int i = 0, size = getChildCount(); i < size; i++) {
+ View c = getChildAt(i);
+ LayoutParams lp = getLayoutParams(c);
+ Group g = horizontal ? lp.columnGroup : lp.rowGroup;
+ Interval span = g.span;
+ int index = leading ? span.min : span.max;
+ margins[index] = max(margins[index], getMargin(c, leading, horizontal));
+ }
+ }
+
+ private int[] getLeadingMargins() {
+ if (leadingMargins == null) {
+ leadingMargins = new int[getCount() + 1];
+ }
+ if (!leadingMarginsValid) {
+ computeMargins(true);
+ leadingMarginsValid = true;
+ }
+ return leadingMargins;
+ }
+
+ private int[] getTrailingMargins() {
+ if (trailingMargins == null) {
+ trailingMargins = new int[getCount() + 1];
+ }
+ if (!trailingMarginsValid) {
+ computeMargins(false);
+ trailingMarginsValid = true;
+ }
+ return trailingMargins;
+ }
+
+ private void addMargins() {
+ int[] leadingMargins = getLeadingMargins();
+ int[] trailingMargins = getTrailingMargins();
+
+ int delta = 0;
+ for (int i = 0, N = getCount(); i < N; i++) {
+ int margins = leadingMargins[i] + trailingMargins[i + 1];
+ delta += margins;
+ minima[i + 1] += delta;
+ }
+ }
+
+ private int getLocationIncludingMargin(View view, boolean leading, int index) {
+ int location = locations[index];
+ int margin;
+ if (!mMarginsIncludedInAlignment) {
+ margin = (leading ? leadingMargins : trailingMargins)[index];
+ } else {
+ margin = 0;
+ }
+ return leading ? (location + margin) : (location - margin);
+ }
+
+ private void computeMinima(int[] a) {
+ Arrays.fill(a, MIN_VALUE);
+ a[0] = 0;
+ solve(getArcs(), a);
+ if (!mMarginsIncludedInAlignment) {
+ addMargins();
+ }
+ }
+
+ private int[] getMinima() {
+ if (minima == null) {
+ int N = getCount() + 1;
+ minima = new int[N];
+ }
+ if (!minimaValid) {
+ computeMinima(minima);
+ minimaValid = true;
+ }
+ return minima;
+ }
+
+ private void computeWeights() {
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ LayoutParams lp = getLayoutParams(getChildAt(i));
+ Group g = horizontal ? lp.columnGroup : lp.rowGroup;
+ Interval span = g.span;
+ int penultimateIndex = span.max - 1;
+ weights[penultimateIndex] += horizontal ? lp.columnWeight : lp.rowWeight;
+ }
+ }
+
+ private float[] getWeights() {
+ if (weights == null) {
+ int N = getCount();
+ weights = new float[N];
+ }
+ computeWeights();
+ return weights;
+ }
+
+ private int[] getLocations() {
+ if (locations == null) {
+ int N = getCount() + 1;
+ locations = new int[N];
+ }
+ return locations;
+ }
+
+ // External entry points
+
+ private int size(int[] locations) {
+ return locations[locations.length - 1] - locations[0];
+ }
+
+ private int getMin() {
+ return size(getMinima());
+ }
+
+ private void layout(int targetSize) {
+ int[] mins = getMinima();
+
+ int totalDelta = max(0, targetSize - size(mins)); // confine to expansion
+
+ float[] weights = getWeights();
+ float totalWeight = sum(weights);
+
+ if (totalWeight == 0f && weights.length > 0) {
+ weights[weights.length - 1] = 1;
+ totalWeight = 1;
+ }
+
+ int[] locations = getLocations();
+ int cumulativeDelta = 0;
+
+ // note |weights| = |locations| - 1
+ for (int i = 0; i < weights.length; i++) {
+ float weight = weights[i];
+ int delta = (int) (totalDelta * weight / totalWeight);
+ cumulativeDelta += delta;
+ locations[i + 1] = mins[i + 1] + cumulativeDelta;
+
+ totalDelta -= delta;
+ totalWeight -= weight;
+ }
+ }
+
+ private void invalidateStructure() {
+ countValid = false;
+
+ groupBounds = null;
+ spanSizes = null;
+ leadingMargins = null;
+ trailingMargins = null;
+ minima = null;
+ weights = null;
+ locations = null;
+
+ invalidateValues();
+ }
+
+ private void invalidateValues() {
+ groupBoundsValid = false;
+ spanSizesValid = false;
+ arcsValid = false;
+ leadingMarginsValid = false;
+ trailingMarginsValid = false;
+ minimaValid = false;
+ }
+ }
+
+ /**
+ * Layout information associated with each of the children of a GridLayout.
+ * <p>
+ * GridLayout supports both row and column spanning and arbitrary forms of alignment within
+ * each cell group. The fundamental parameters associated with each cell group are
+ * gathered into their vertical and horizontal components and stored
+ * in the {@link #rowGroup} and {@link #columnGroup} layout parameters.
+ * {@link Group Groups} are immutable structures and may be shared between the layout
+ * parameters of different children.
+ * <p>
+ * The row and column groups contain the leading and trailing indices along each axis
+ * and together specify the four grid indices that delimit the cells of this cell group.
+ * <p>
+ * The {@link Group#alignment alignment} fields of the row and column groups together specify
+ * both aspects of alignment within the cell group. It is also possible to specify a child's
+ * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
+ * method.
+ * <p>
+ * See {@link GridLayout} for a description of the conventions used by GridLayout
+ * in reference to grid indices.
+ *
+ * <h4>Default values</h4>
+ *
+ * <ul>
+ * <li>{@link #width} = {@link #WRAP_CONTENT}</li>
+ * <li>{@link #height} = {@link #WRAP_CONTENT}</li>
+ * <li>{@link #topMargin} = 0 when
+ * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+ * {@code false}; otherwise {@link #UNDEFINED}, to
+ * indicate that a default value should be computed on demand. </li>
+ * <li>{@link #leftMargin} = 0 when
+ * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+ * {@code false}; otherwise {@link #UNDEFINED}, to
+ * indicate that a default value should be computed on demand. </li>
+ * <li>{@link #bottomMargin} = 0 when
+ * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+ * {@code false}; otherwise {@link #UNDEFINED}, to
+ * indicate that a default value should be computed on demand. </li>
+ * <li>{@link #rightMargin} = 0 when
+ * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+ * {@code false}; otherwise {@link #UNDEFINED}, to
+ * indicate that a default value should be computed on demand. </li>
+ * <li>{@link #rowGroup}{@code .span} = {@code [0, 1]} </li>
+ * <li>{@link #rowGroup}{@code .alignment} = {@link #BASELINE} </li>
+ * <li>{@link #columnGroup}{@code .span} = {@code [0, 1]} </li>
+ * <li>{@link #columnGroup}{@code .alignment} = {@link #LEFT} </li>
+ * <li>{@link #rowWeight} = {@code 0f} </li>
+ * <li>{@link #columnWeight} = {@code 0f} </li>
+ * </ul>
+ *
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_row
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_column
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
+ */
+ public static class LayoutParams extends MarginLayoutParams {
+
+ // Default values
+
+ private static final int DEFAULT_WIDTH = WRAP_CONTENT;
+ private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
+ private static final int DEFAULT_MARGIN = UNDEFINED;
+ private static final int DEFAULT_ROW = UNDEFINED;
+ private static final int DEFAULT_COLUMN = UNDEFINED;
+ private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
+ private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
+ private static final Alignment DEFAULT_COLUMN_ALIGNMENT = LEFT;
+ private static final Alignment DEFAULT_ROW_ALIGNMENT = BASELINE;
+ private static final Group DEFAULT_COLUMN_GROUP =
+ new Group(DEFAULT_SPAN, DEFAULT_COLUMN_ALIGNMENT);
+ private static final Group DEFAULT_ROW_GROUP =
+ new Group(DEFAULT_SPAN, DEFAULT_ROW_ALIGNMENT);
+ private static final int DEFAULT_WEIGHT_0 = 0;
+ private static final int DEFAULT_WEIGHT_1 = 1;
+
+ // Misc
+
+ private static final Rect CONTAINER_BOUNDS = new Rect(0, 0, 2, 2);
+ private static final Alignment[] COLUMN_ALIGNMENTS = { LEFT, CENTER, RIGHT };
+ private static final Alignment[] ROW_ALIGNMENTS = { TOP, CENTER, BOTTOM };
+
+ // TypedArray indices
+
+ private static final int MARGIN = styleable.ViewGroup_MarginLayout_layout_margin;
+ private static final int LEFT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginLeft;
+ private static final int TOP_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginTop;
+ private static final int RIGHT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginRight;
+ private static final int BOTTOM_MARGIN =
+ styleable.ViewGroup_MarginLayout_layout_marginBottom;
+
+ private static final int COLUMN = styleable.GridLayout_Layout_layout_column;
+ private static final int COLUMN_SPAN = styleable.GridLayout_Layout_layout_columnSpan;
+ private static final int COLUMN_WEIGHT = styleable.GridLayout_Layout_layout_columnWeight;
+ private static final int ROW = styleable.GridLayout_Layout_layout_row;
+ private static final int ROW_SPAN = styleable.GridLayout_Layout_layout_rowSpan;
+ private static final int ROW_WEIGHT = styleable.GridLayout_Layout_layout_rowWeight;
+ private static final int GRAVITY = styleable.GridLayout_Layout_layout_gravity;
+
+ // Instance variables
+
+ /**
+ * The group that specifies the vertical characteristics of the cell group
+ * described by these layout parameters.
+ */
+ public Group rowGroup;
+ /**
+ * The group that specifies the horizontal characteristics of the cell group
+ * described by these layout parameters.
+ */
+ public Group columnGroup;
+ /**
+ * The proportional space that should be taken by the associated row group
+ * during excess space distribution.
+ */
+ public float rowWeight;
+ /**
+ * The proportional space that should be taken by the associated column group
+ * during excess space distribution.
+ */
+ public float columnWeight;
+
+ // Constructors
+
+ private LayoutParams(
+ int width, int height,
+ int left, int top, int right, int bottom,
+ Group rowGroup, Group columnGroup, float rowWeight, float columnWeight) {
+ super(width, height);
+ setMargins(left, top, right, bottom);
+ this.rowGroup = rowGroup;
+ this.columnGroup = columnGroup;
+ this.rowWeight = rowWeight;
+ this.columnWeight = columnWeight;
+ }
+
+ /**
+ * Constructs a new LayoutParams instance for this <code>rowGroup</code>
+ * and <code>columnGroup</code>. All other fields are initialized with
+ * default values as defined in {@link LayoutParams}.
+ *
+ * @param rowGroup the rowGroup
+ * @param columnGroup the columnGroup
+ */
+ public LayoutParams(Group rowGroup, Group columnGroup) {
+ this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
+ rowGroup, columnGroup, DEFAULT_WEIGHT_0, DEFAULT_WEIGHT_0);
+ }
+
+ /**
+ * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
+ */
+ public LayoutParams() {
+ this(DEFAULT_ROW_GROUP, DEFAULT_COLUMN_GROUP);
+ }
+
+ // Copying constructors
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(ViewGroup.LayoutParams params) {
+ super(params);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(MarginLayoutParams params) {
+ super(params);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(LayoutParams that) {
+ super(that);
+ this.columnGroup = that.columnGroup;
+ this.rowGroup = that.rowGroup;
+ this.columnWeight = that.columnWeight;
+ this.rowWeight = that.rowWeight;
+ }
+
+ // AttributeSet constructors
+
+ private LayoutParams(Context context, AttributeSet attrs, int defaultGravity) {
+ super(context, attrs);
+ reInitSuper(context, attrs);
+ init(context, attrs, defaultGravity);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Values not defined in the attribute set take the default values
+ * defined in {@link LayoutParams}.
+ */
+ public LayoutParams(Context context, AttributeSet attrs) {
+ this(context, attrs, Gravity.NO_GRAVITY);
+ }
+
+ // Implementation
+
+ private static boolean definesVertical(int gravity) {
+ return gravity > 0 && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != 0;
+ }
+
+ private static boolean definesHorizontal(int gravity) {
+ return gravity > 0 && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 0;
+ }
+
+ private static <T> T getAlignment(T[] alignments, T fill, int min, int max,
+ boolean isUndefined, T defaultValue) {
+ if (isUndefined) {
+ return defaultValue;
+ }
+ return min != max ? fill : alignments[min];
+ }
+
+ // Reinitialise the margins using a different default policy than MarginLayoutParams.
+ // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
+ // so that a layout manager default can be accessed post set up. We need this as, at the
+ // point of installation, we do not know how many rows/cols there are and therefore
+ // which elements are positioned next to the container's trailing edges. We need to
+ // know this as margins around the container's boundary should have different
+ // defaults to those between peers.
+
+ // This method could be parametrized and moved into MarginLayout.
+ private void reInitSuper(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, styleable.ViewGroup_MarginLayout);
+ try {
+ int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
+
+ this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
+ this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
+ this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
+ this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ // Gravity. For conversion from the static the integers defined in the Gravity class,
+ // use Gravity.apply() to apply gravity to a view of zero size and see where it ends up.
+ private static Alignment getColumnAlignment(int gravity, int width) {
+ Rect r = new Rect(0, 0, 0, 0);
+ Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r);
+
+ boolean fill = (width == MATCH_PARENT);
+ Alignment defaultAlignment = fill ? FILL : DEFAULT_COLUMN_ALIGNMENT;
+ return getAlignment(COLUMN_ALIGNMENTS, FILL, r.left, r.right,
+ !definesHorizontal(gravity), defaultAlignment);
+ }
+
+ private static Alignment getRowAlignment(int gravity, int height) {
+ Rect r = new Rect(0, 0, 0, 0);
+ Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r);
+
+ boolean fill = (height == MATCH_PARENT);
+ Alignment defaultAlignment = fill ? FILL : DEFAULT_ROW_ALIGNMENT;
+ return getAlignment(ROW_ALIGNMENTS, FILL, r.top, r.bottom,
+ !definesVertical(gravity), defaultAlignment);
+ }
+
+ private int getDefaultWeight(int size) {
+ return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0;
+ }
+
+ private void init(Context context, AttributeSet attrs, int defaultGravity) {
+ TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout_Layout);
+ try {
+ int gravity = a.getInteger(GRAVITY, defaultGravity);
+
+ int column = a.getInteger(COLUMN, DEFAULT_COLUMN);
+ int columnSpan = a.getInteger(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
+ Interval hSpan = new Interval(column, column + columnSpan);
+ this.columnGroup = new Group(hSpan, getColumnAlignment(gravity, width));
+ this.columnWeight = a.getFloat(COLUMN_WEIGHT, getDefaultWeight(width));
+
+ int row = a.getInteger(ROW, DEFAULT_ROW);
+ int rowSpan = a.getInteger(ROW_SPAN, DEFAULT_SPAN_SIZE);
+ Interval vSpan = new Interval(row, row + rowSpan);
+ this.rowGroup = new Group(vSpan, getRowAlignment(gravity, height));
+ this.rowWeight = a.getFloat(ROW_WEIGHT, getDefaultWeight(height));
+ } finally {
+ a.recycle();
+ }
+ }
+
+ /**
+ * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
+ * See {@link android.view.Gravity}.
+ *
+ * @param gravity the new gravity value
+ *
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
+ */
+ public void setGravity(int gravity) {
+ columnGroup = columnGroup.copyWriteAlignment(getColumnAlignment(gravity, width));
+ rowGroup = rowGroup.copyWriteAlignment(getRowAlignment(gravity, height));
+ }
+
+ @Override
+ protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
+ this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
+ this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
+ }
+
+ private void setRowGroupSpan(Interval span) {
+ rowGroup = rowGroup.copyWriteSpan(span);
+ }
+
+ private void setColumnGroupSpan(Interval span) {
+ columnGroup = columnGroup.copyWriteSpan(span);
+ }
+ }
+
+ /*
+ In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
+ Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
+ */
+ private static class Arc {
+ public final Interval span;
+ public final MutableInt value;
+ public boolean completesCycle;
+
+ public Arc(Interval span, MutableInt value) {
+ this.span = span;
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return span + " " + (completesCycle ? "+>" : "->") + " " + value;
+ }
+ }
+
+ // A mutable Integer - used to avoid heap allocation during the layout operation
+
+ private static class MutableInt {
+ public int value;
+
+ private MutableInt() {
+ reset();
+ }
+
+ private MutableInt(int value) {
+ this.value = value;
+ }
+
+ private void reset() {
+ value = Integer.MIN_VALUE;
+ }
+ }
+
+ /*
+ This data structure is used in place of a Map where we have an index that refers to the order
+ in which each key/value pairs were added to the map. In this case we store keys and values
+ in arrays of a length that is equal to the number of unique keys. We also maintain an
+ array of indexes from insertion order to the compacted arrays of keys and values.
+
+ Note that behavior differs from that of a LinkedHashMap in that repeated entries
+ *do* get added multiples times. So the length of index is equals to the number of
+ items added.
+
+ This is useful in the GridLayout class where we can rely on the order of children not
+ changing during layout - to use integer-based lookup for our internal structures
+ rather than using (and storing) an implementation of Map<Key, ?>.
+ */
+ @SuppressWarnings(value = "unchecked")
+ private static class PackedMap<K, V> {
+ public final int[] index;
+ public final K[] keys;
+ public final V[] values;
+
+ private PackedMap(K[] keys, V[] values) {
+ this.index = createIndex(keys);
+
+ this.keys = compact(keys, index);
+ this.values = compact(values, index);
+ }
+
+ private K getKey(int i) {
+ return keys[index[i]];
+ }
+
+ private V getValue(int i) {
+ return values[index[i]];
+ }
+
+ private static <K> int[] createIndex(K[] keys) {
+ int size = keys.length;
+ int[] result = new int[size];
+
+ Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
+ for (int i = 0; i < size; i++) {
+ K key = keys[i];
+ Integer index = keyToIndex.get(key);
+ if (index == null) {
+ index = keyToIndex.size();
+ keyToIndex.put(key, index);
+ }
+ result[i] = index;
+ }
+ return result;
+ }
+
+ private static int max(int[] a, int valueIfEmpty) {
+ int result = valueIfEmpty;
+ for (int i = 0, length = a.length; i < length; i++) {
+ result = Math.max(result, a[i]);
+ }
+ return result;
+ }
+
+ /*
+ Create a compact array of keys or values using the supplied index.
+ */
+ private static <K> K[] compact(K[] a, int[] index) {
+ int size = a.length;
+ Class<?> componentType = a.getClass().getComponentType();
+ K[] result = (K[]) Array.newInstance(componentType, max(index, -1) + 1);
+
+ // this overwrite duplicates, retaining the last equivalent entry
+ for (int i = 0; i < size; i++) {
+ result[index[i]] = a[i];
+ }
+ return result;
+ }
+ }
+
+ /*
+ For each Group (with a given alignment) we need to store the amount of space required
+ before the alignment point and the amount of space required after it. One side of this
+ calculation is always 0 for LEADING and TRAILING alignments but we don't make use of this.
+ For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no
+ simple optimisations are possible.
+
+ The general algorithm therefore is to create a Map (actually a PackedMap) from
+ Group to Bounds and to loop through all Views in the group taking the maximum
+ of the values for each View.
+ */
+ private static class Bounds {
+ public int before;
+ public int after;
+
+ private Bounds() {
+ reset();
+ }
+
+ private void reset() {
+ before = Integer.MIN_VALUE;
+ after = Integer.MIN_VALUE;
+ }
+
+ private void include(int before, int after) {
+ this.before = max(this.before, before);
+ this.after = max(this.after, after);
+ }
+
+ private int size() {
+ return before + after;
+ }
+
+ @Override
+ public String toString() {
+ return "Bounds{" +
+ "before=" + before +
+ ", after=" + after +
+ '}';
+ }
+ }
+
+ /**
+ * An Interval represents a contiguous range of values that lie between
+ * the interval's {@link #min} and {@link #max} values.
+ * <p>
+ * Intervals are immutable so may be passed as values and used as keys in hash tables.
+ * It is not necessary to have multiple instances of Intervals which have the same
+ * {@link #min} and {@link #max} values.
+ * <p>
+ * Intervals are often written as {@code [min, max]} and represent the set of values
+ * {@code x} such that {@code min <= x < max}.
+ */
+ /* package */ static class Interval {
+ /**
+ * The minimum value.
+ */
+ public final int min;
+
+ /**
+ * The maximum value.
+ */
+ public final int max;
+
+ /**
+ * Construct a new Interval, {@code interval}, where:
+ * <ul>
+ * <li> {@code interval.min = min} </li>
+ * <li> {@code interval.max = max} </li>
+ * </ul>
+ *
+ * @param min the minimum value.
+ * @param max the maximum value.
+ */
+ public Interval(int min, int max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ private int size() {
+ return max - min;
+ }
+
+ private Interval inverse() {
+ return new Interval(max, min);
+ }
+
+ /**
+ * Returns {@code true} if the {@link #getClass class},
+ * {@link #min} and {@link #max} properties of this Interval and the
+ * supplied parameter are pairwise equal; {@code false} otherwise.
+ *
+ * @param that the object to compare this interval with
+ *
+ * @return {@code true} if the specified object is equal to this
+ * {@code Interval}, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object that) {
+ if (this == that) {
+ return true;
+ }
+ if (that == null || getClass() != that.getClass()) {
+ return false;
+ }
+
+ Interval interval = (Interval) that;
+
+ if (max != interval.max) {
+ return false;
+ }
+ if (min != interval.min) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = min;
+ result = 31 * result + max;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + min + ", " + max + "]";
+ }
+ }
+
+ /**
+ * A group specifies either the horizontal or vertical characteristics of a group of
+ * cells.
+ * <p>
+ * Groups are immutable and so may be shared between views with the same
+ * {@code span} and {@code alignment}.
+ */
+ public static class Group {
+ /**
+ * The grid indices of the leading and trailing edges of this cell group for the
+ * appropriate axis.
+ * <p>
+ * See {@link GridLayout} for a description of the conventions used by GridLayout
+ * for grid indices.
+ */
+ /* package */ final Interval span;
+ /**
+ * Specifies how cells should be aligned in this group.
+ * For row groups, this specifies the vertical alignment.
+ * For column groups, this specifies the horizontal alignment.
+ */
+ public final Alignment alignment;
+
+ /**
+ * Construct a new Group, {@code group}, where:
+ * <ul>
+ * <li> {@code group.span = span} </li>
+ * <li> {@code group.alignment = alignment} </li>
+ * </ul>
+ *
+ * @param span the span
+ * @param alignment the alignment
+ */
+ /* package */ Group(Interval span, Alignment alignment) {
+ this.span = span;
+ this.alignment = alignment;
+ }
+
+ /**
+ * Construct a new Group, {@code group}, where:
+ * <ul>
+ * <li> {@code group.span = [start, start + size]} </li>
+ * <li> {@code group.alignment = alignment} </li>
+ * </ul>
+ *
+ * @param start the start
+ * @param size the size
+ * @param alignment the alignment
+ */
+ public Group(int start, int size, Alignment alignment) {
+ this(new Interval(start, start + size), alignment);
+ }
+
+ /**
+ * Construct a new Group, {@code group}, where:
+ * <ul>
+ * <li> {@code group.span = [start, start + 1]} </li>
+ * <li> {@code group.alignment = alignment} </li>
+ * </ul>
+ *
+ * @param start the start index
+ * @param alignment the alignment
+ */
+ public Group(int start, Alignment alignment) {
+ this(start, 1, alignment);
+ }
+
+ private Group copyWriteSpan(Interval span) {
+ return new Group(span, alignment);
+ }
+
+ private Group copyWriteAlignment(Alignment alignment) {
+ return new Group(span, alignment);
+ }
+
+ /**
+ * Returns {@code true} if the {@link #getClass class}, {@link #alignment} and {@code span}
+ * properties of this Group and the supplied parameter are pairwise equal,
+ * {@code false} otherwise.
+ *
+ * @param that the object to compare this group with
+ *
+ * @return {@code true} if the specified object is equal to this
+ * {@code Group}; {@code false} otherwise
+ */
+ @Override
+ public boolean equals(Object that) {
+ if (this == that) {
+ return true;
+ }
+ if (that == null || getClass() != that.getClass()) {
+ return false;
+ }
+
+ Group group = (Group) that;
+
+ if (!alignment.equals(group.alignment)) {
+ return false;
+ }
+ if (!span.equals(group.span)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = span.hashCode();
+ result = 31 * result + alignment.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * Alignments specify where a view should be placed within a cell group and
+ * what size it should be.
+ * <p>
+ * The {@link LayoutParams} class contains a {@link LayoutParams#rowGroup rowGroup}
+ * and a {@link LayoutParams#columnGroup columnGroup} each of which contains an
+ * {@link Group#alignment alignment}. Overall placement of the view in the cell
+ * group is specified by the two alignments which act along each axis independently.
+ * <p>
+ * An Alignment implementation must define the {@link #getAlignmentValue(View, int)}
+ * to return the appropriate value for the type of alignment being defined.
+ * The enclosing algorithms position the children
+ * so that the values returned from the alignment
+ * are the same for all of the views in a group.
+ * <p>
+ * The GridLayout class defines the most common alignments used in general layout:
+ * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link
+ * #BASELINE} and {@link #FILL}.
+ */
+ public static interface Alignment {
+ /**
+ * Returns an alignment value. In the case of vertical alignments the value
+ * returned should indicate the distance from the top of the view to the
+ * alignment location.
+ * For horizontal alignments measurement is made from the left edge of the component.
+ *
+ * @param view the view to which this alignment should be applied
+ * @param viewSize the measured size of the view
+ * @return the alignment value
+ */
+ public int getAlignmentValue(View view, int viewSize);
+
+ /**
+ * Returns the size of the view specified by this alignment.
+ * In the case of vertical alignments this method should return a height; for
+ * horizontal alignments this method should return the width.
+ *
+ * @param view the view to which this alignment should be applied
+ * @param viewSize the measured size of the view
+ * @param cellSize the size of the cell into which this view will be placed
+ * @return the aligned size
+ */
+ public int getSizeInCell(View view, int viewSize, int cellSize);
+ }
+
+ private static abstract class AbstractAlignment implements Alignment {
+ public int getSizeInCell(View view, int viewSize, int cellSize) {
+ return viewSize;
+ }
+ }
+
+ private static final Alignment LEADING = new AbstractAlignment() {
+ public int getAlignmentValue(View view, int viewSize) {
+ return 0;
+ }
+
+ };
+
+ private static final Alignment TRAILING = new AbstractAlignment() {
+ public int getAlignmentValue(View view, int viewSize) {
+ return viewSize;
+ }
+ };
+
+ /**
+ * Indicates that a view should be aligned with the <em>top</em>
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment TOP = LEADING;
+
+ /**
+ * Indicates that a view should be aligned with the <em>bottom</em>
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment BOTTOM = TRAILING;
+
+ /**
+ * Indicates that a view should be aligned with the <em>right</em>
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment RIGHT = TRAILING;
+
+ /**
+ * Indicates that a view should be aligned with the <em>left</em>
+ * edges of the other views in its cell group.
+ */
+ public static final Alignment LEFT = LEADING;
+
+ /**
+ * Indicates that a view should be <em>centered</em> with the other views in its cell group.
+ * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and {@link
+ * LayoutParams#columnGroup columnGroups}.
+ */
+ public static final Alignment CENTER = new AbstractAlignment() {
+ public int getAlignmentValue(View view, int viewSize) {
+ return viewSize >> 1;
+ }
+ };
+
+ /**
+ * Indicates that a view should be aligned with the <em>baselines</em>
+ * of the other views in its cell group.
+ * This constant may only be used as an alignment in {@link LayoutParams#rowGroup rowGroups}.
+ *
+ * @see View#getBaseline()
+ */
+ public static final Alignment BASELINE = new AbstractAlignment() {
+ public int getAlignmentValue(View view, int height) {
+ if (view == null) {
+ return UNDEFINED;
+ }
+ int baseline = view.getBaseline();
+ if (baseline == -1) {
+ return UNDEFINED;
+ } else {
+ return baseline;
+ }
+ }
+
+ };
+
+ /**
+ * Indicates that a view should expanded to fit the boundaries of its cell group.
+ * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and
+ * {@link LayoutParams#columnGroup columnGroups}.
+ */
+ public static final Alignment FILL = new Alignment() {
+ public int getAlignmentValue(View view, int viewSize) {
+ return UNDEFINED;
+ }
+
+ public int getSizeInCell(View view, int viewSize, int cellSize) {
+ return cellSize;
+ }
+ };
+} \ No newline at end of file
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 0383b5c..732cedc 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1408,19 +1408,20 @@ public class GridView extends AbsListView {
int childLeft;
final int childTop = flow ? y : y - h;
- switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- childLeft = childrenLeft;
- break;
- case Gravity.CENTER_HORIZONTAL:
- childLeft = childrenLeft + ((mColumnWidth - w) / 2);
- break;
- case Gravity.RIGHT:
- childLeft = childrenLeft + mColumnWidth - w;
- break;
- default:
- childLeft = childrenLeft;
- break;
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity,isLayoutRtl());
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ childLeft = childrenLeft;
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = childrenLeft + ((mColumnWidth - w) / 2);
+ break;
+ case Gravity.RIGHT:
+ childLeft = childrenLeft + mColumnWidth - w;
+ break;
+ default:
+ childLeft = childrenLeft;
+ break;
}
if (needToMeasure) {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 1fe6f4b..4b870ec 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -187,6 +187,11 @@ public class ImageView extends View {
}
@Override
+ public boolean isLayoutRtl(Drawable dr) {
+ return (dr == mDrawable) ? isLayoutRtl() : super.isLayoutRtl(dr);
+ }
+
+ @Override
protected boolean onSetAlpha(int alpha) {
if (getBackground() == null) {
int scale = alpha + (alpha >> 7);
@@ -218,15 +223,16 @@ public class ImageView extends View {
/**
* An optional argument to supply a maximum width for this view. Only valid if
- * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x
- * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to
- * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to
- * WRAP_CONTENT.
+ * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum
+ * of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
+ * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
+ * layout params to WRAP_CONTENT.
*
* <p>
* Note that this view could be still smaller than 100 x 100 using this approach if the original
* image is small. To set an image to a fixed size, specify that size in the layout params and
- * then use {@link #setScaleType} to determine how to fit the image within the bounds.
+ * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
+ * the image within the bounds.
* </p>
*
* @param maxWidth maximum width for this view
@@ -240,15 +246,16 @@ public class ImageView extends View {
/**
* An optional argument to supply a maximum height for this view. Only valid if
- * {@link #setAdjustViewBounds} has been set to true. To set an image to be a maximum of 100 x
- * 100 while preserving the original aspect ratio, do the following: 1) set adjustViewBounds to
- * true 2) set maxWidth and maxHeight to 100 3) set the height and width layout params to
- * WRAP_CONTENT.
+ * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a
+ * maximum of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
+ * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
+ * layout params to WRAP_CONTENT.
*
* <p>
* Note that this view could be still smaller than 100 x 100 using this approach if the original
* image is small. To set an image to a fixed size, specify that size in the layout params and
- * then use {@link #setScaleType} to determine how to fit the image within the bounds.
+ * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
+ * the image within the bounds.
* </p>
*
* @param maxHeight maximum height for this view
@@ -272,8 +279,8 @@ public class ImageView extends View {
*
* <p class="note">This does Bitmap reading and decoding on the UI
* thread, which can cause a latency hiccup. If that's a concern,
- * consider using {@link #setImageDrawable} or
- * {@link #setImageBitmap} and
+ * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
+ * {@link #setImageBitmap(android.graphics.Bitmap)} and
* {@link android.graphics.BitmapFactory} instead.</p>
*
* @param resId the resource identifier of the the drawable
@@ -297,8 +304,8 @@ public class ImageView extends View {
*
* <p class="note">This does Bitmap reading and decoding on the UI
* thread, which can cause a latency hiccup. If that's a concern,
- * consider using {@link #setImageDrawable} or
- * {@link #setImageBitmap} and
+ * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
+ * {@link #setImageBitmap(android.graphics.Bitmap)} and
* {@link android.graphics.BitmapFactory} instead.</p>
*
* @param uri The Uri of an image
@@ -902,12 +909,12 @@ public class ImageView extends View {
/**
* <p>Set the offset of the widget's text baseline from the widget's top
- * boundary. This value is overridden by the {@link #setBaselineAlignBottom}
+ * boundary. This value is overridden by the {@link #setBaselineAlignBottom(boolean)}
* property.</p>
*
* @param baseline The baseline to use, or -1 if none is to be provided.
*
- * @see #setBaseline
+ * @see #setBaseline(int)
* @attr ref android.R.styleable#ImageView_baseline
*/
public void setBaseline(int baseline) {
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index fd0e53d..0cdbc5b 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -103,21 +103,39 @@ public class LinearLayout extends ViewGroup {
@ViewDebug.ExportedProperty(category = "measurement")
private int mOrientation;
- @ViewDebug.ExportedProperty(category = "measurement", mapping = {
- @ViewDebug.IntToString(from = -1, to = "NONE"),
- @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
- @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
- @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
- @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
- @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
- @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
- @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
- @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
- @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
- @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
- @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
+ @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+ @ViewDebug.FlagToString(mask = -1,
+ equals = -1, name = "NONE"),
+ @ViewDebug.FlagToString(mask = Gravity.NO_GRAVITY,
+ equals = Gravity.NO_GRAVITY,name = "NONE"),
+ @ViewDebug.FlagToString(mask = Gravity.TOP,
+ equals = Gravity.TOP, name = "TOP"),
+ @ViewDebug.FlagToString(mask = Gravity.BOTTOM,
+ equals = Gravity.BOTTOM, name = "BOTTOM"),
+ @ViewDebug.FlagToString(mask = Gravity.LEFT,
+ equals = Gravity.LEFT, name = "LEFT"),
+ @ViewDebug.FlagToString(mask = Gravity.RIGHT,
+ equals = Gravity.RIGHT, name = "RIGHT"),
+ @ViewDebug.FlagToString(mask = Gravity.START,
+ equals = Gravity.START, name = "START"),
+ @ViewDebug.FlagToString(mask = Gravity.END,
+ equals = Gravity.END, name = "END"),
+ @ViewDebug.FlagToString(mask = Gravity.CENTER_VERTICAL,
+ equals = Gravity.CENTER_VERTICAL, name = "CENTER_VERTICAL"),
+ @ViewDebug.FlagToString(mask = Gravity.FILL_VERTICAL,
+ equals = Gravity.FILL_VERTICAL, name = "FILL_VERTICAL"),
+ @ViewDebug.FlagToString(mask = Gravity.CENTER_HORIZONTAL,
+ equals = Gravity.CENTER_HORIZONTAL, name = "CENTER_HORIZONTAL"),
+ @ViewDebug.FlagToString(mask = Gravity.FILL_HORIZONTAL,
+ equals = Gravity.FILL_HORIZONTAL, name = "FILL_HORIZONTAL"),
+ @ViewDebug.FlagToString(mask = Gravity.CENTER,
+ equals = Gravity.CENTER, name = "CENTER"),
+ @ViewDebug.FlagToString(mask = Gravity.FILL,
+ equals = Gravity.FILL, name = "FILL"),
+ @ViewDebug.FlagToString(mask = Gravity.RELATIVE_LAYOUT_DIRECTION,
+ equals = Gravity.RELATIVE_LAYOUT_DIRECTION, name = "RELATIVE")
})
- private int mGravity = Gravity.LEFT | Gravity.TOP;
+ private int mGravity = Gravity.START | Gravity.TOP;
@ViewDebug.ExportedProperty(category = "measurement")
private int mTotalLength;
@@ -201,6 +219,11 @@ public class LinearLayout extends ViewGroup {
mShowDividers = showDividers;
}
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
/**
* @return A flag set indicating how dividers should be shown around items.
* @see #setShowDividers(int)
@@ -230,6 +253,39 @@ public class LinearLayout extends ViewGroup {
requestLayout();
}
+ /**
+ * Set padding displayed on both ends of dividers.
+ *
+ * @param padding Padding value in pixels that will be applied to each end
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #getDividerPadding()
+ */
+ public void setDividerPadding(int padding) {
+ mDividerPadding = padding;
+ }
+
+ /**
+ * Get the padding size used to inset dividers in pixels
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #setDividerPadding(int)
+ */
+ public int getDividerPadding() {
+ return mDividerPadding;
+ }
+
+ /**
+ * Get the width of the current divider drawable.
+ *
+ * @hide Used internally by framework.
+ */
+ public int getDividerWidth() {
+ return mDividerWidth;
+ }
+
@Override
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
@@ -244,29 +300,15 @@ public class LinearLayout extends ViewGroup {
}
void drawDividersVertical(Canvas canvas) {
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
- final boolean showDividerEnd =
- (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
final int count = getVirtualChildCount();
int top = getPaddingTop();
- boolean firstVisible = true;
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
top += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- drawHorizontalDivider(canvas, top);
- top += mDividerHeight;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
drawHorizontalDivider(canvas, top);
top += mDividerHeight;
}
@@ -276,35 +318,21 @@ public class LinearLayout extends ViewGroup {
}
}
- if (showDividerEnd) {
+ if (hasDividerBeforeChildAt(count)) {
drawHorizontalDivider(canvas, top);
}
}
void drawDividersHorizontal(Canvas canvas) {
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
- final boolean showDividerEnd =
- (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
final int count = getVirtualChildCount();
int left = getPaddingLeft();
- boolean firstVisible = true;
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
left += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- drawVerticalDivider(canvas, left);
- left += mDividerWidth;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
drawVerticalDivider(canvas, left);
left += mDividerWidth;
}
@@ -314,7 +342,7 @@ public class LinearLayout extends ViewGroup {
}
}
- if (showDividerEnd) {
+ if (hasDividerBeforeChildAt(count)) {
drawVerticalDivider(canvas, left);
}
}
@@ -523,6 +551,23 @@ public class LinearLayout extends ViewGroup {
}
/**
+ * Determines where to position dividers between children.
+ *
+ * @param childIndex Index of child to check for preceding divider
+ * @return true if there should be a divider before the child at childIndex
+ * @hide Pending API consideration. Currently only used internally by the system.
+ */
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ if (childIndex == 0) {
+ return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
+ } else if (childIndex == getChildCount()) {
+ return (mShowDividers & SHOW_DIVIDER_END) != 0;
+ } else {
+ return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
+ }
+ }
+
+ /**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
@@ -554,14 +599,7 @@ public class LinearLayout extends ViewGroup {
int largestChildHeight = Integer.MIN_VALUE;
- // A divider at the end will change how much space views can consume.
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
// See how tall everyone is. Also remember max width.
- boolean firstVisible = true;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
@@ -575,12 +613,7 @@ public class LinearLayout extends ViewGroup {
continue;
}
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- mTotalLength += mDividerHeight;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
@@ -677,11 +710,12 @@ public class LinearLayout extends ViewGroup {
i += getChildrenSkipCount(child, i);
}
- if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
- if (useLargestChild && heightMode == MeasureSpec.AT_MOST) {
+ if (useLargestChild &&
+ (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
@@ -794,6 +828,31 @@ public class LinearLayout extends ViewGroup {
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
+
+
+ // We have no limit, so make all weighted views as tall as the largest child.
+ // Children will have already been measured once.
+ if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(largestChildHeight,
+ MeasureSpec.EXACTLY));
+ }
+ }
+ }
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
@@ -881,14 +940,7 @@ public class LinearLayout extends ViewGroup {
int largestChildWidth = Integer.MIN_VALUE;
- // A divider at the end will change how much space views can consume.
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
// See how wide everyone is. Also remember max height.
- boolean firstVisible = true;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
@@ -902,12 +954,7 @@ public class LinearLayout extends ViewGroup {
continue;
}
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- mTotalLength += mDividerWidth;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerWidth;
}
@@ -1022,7 +1069,7 @@ public class LinearLayout extends ViewGroup {
i += getChildrenSkipCount(child, i);
}
- if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerWidth;
}
@@ -1041,7 +1088,8 @@ public class LinearLayout extends ViewGroup {
maxHeight = Math.max(maxHeight, ascent + descent);
}
- if (useLargestChild && widthMode == MeasureSpec.AT_MOST) {
+ if (useLargestChild &&
+ (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
@@ -1197,6 +1245,29 @@ public class LinearLayout extends ViewGroup {
}
} else {
alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);
+
+ // We have no limit, so make all weighted views as wide as the largest child.
+ // Children will have already been measured once.
+ if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
+ MeasureSpec.EXACTLY));
+ }
+ }
+ }
}
if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
@@ -1328,7 +1399,7 @@ public class LinearLayout extends ViewGroup {
void layoutVertical() {
final int paddingLeft = mPaddingLeft;
- int childTop = mPaddingTop;
+ int childTop;
int childLeft;
// Where right end of child should go
@@ -1341,28 +1412,23 @@ public class LinearLayout extends ViewGroup {
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
- final int minorGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-
- if (majorGravity != Gravity.TOP) {
- switch (majorGravity) {
- case Gravity.BOTTOM:
- // mTotalLength contains the padding already, we add the top
- // padding to compensate
- childTop = mBottom - mTop + mPaddingTop - mTotalLength;
- break;
-
- case Gravity.CENTER_VERTICAL:
- childTop += ((mBottom - mTop) - mTotalLength) / 2;
- break;
- }
-
- }
-
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
- if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
- childTop += mDividerHeight;
+ final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+ switch (majorGravity) {
+ case Gravity.BOTTOM:
+ // mTotalLength contains the padding already
+ childTop = mPaddingTop + mBottom - mTop - mTotalLength;
+ break;
+
+ // mTotalLength contains the padding already
+ case Gravity.CENTER_VERTICAL:
+ childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
+ break;
+
+ case Gravity.TOP:
+ default:
+ childTop = mPaddingTop;
+ break;
}
for (int i = 0; i < count; i++) {
@@ -1380,12 +1446,8 @@ public class LinearLayout extends ViewGroup {
if (gravity < 0) {
gravity = minorGravity;
}
-
- switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- childLeft = paddingLeft + lp.leftMargin;
- break;
-
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
@@ -1394,20 +1456,22 @@ public class LinearLayout extends ViewGroup {
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
+
+ case Gravity.LEFT:
default:
- childLeft = paddingLeft;
+ childLeft = paddingLeft + lp.leftMargin;
break;
}
-
+
+ if (hasDividerBeforeChildAt(i)) {
+ childTop += mDividerHeight;
+ }
+
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
- if (showDividerMiddle) {
- childTop += mDividerHeight;
- }
-
i += getChildrenSkipCount(child, i);
}
}
@@ -1422,10 +1486,11 @@ public class LinearLayout extends ViewGroup {
* @see #onLayout(boolean, int, int, int, int)
*/
void layoutHorizontal() {
+ final boolean isLayoutRtl = isLayoutRtl();
final int paddingTop = mPaddingTop;
int childTop;
- int childLeft = mPaddingLeft;
+ int childLeft;
// Where bottom of child should go
final int height = mBottom - mTop;
@@ -1436,7 +1501,7 @@ public class LinearLayout extends ViewGroup {
final int count = getVirtualChildCount();
- final int majorGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final boolean baselineAligned = mBaselineAligned;
@@ -1444,32 +1509,37 @@ public class LinearLayout extends ViewGroup {
final int[] maxAscent = mMaxAscent;
final int[] maxDescent = mMaxDescent;
- if (majorGravity != Gravity.LEFT) {
- switch (majorGravity) {
- case Gravity.RIGHT:
- // mTotalLength contains the padding already, we add the left
- // padding to compensate
- childLeft = mRight - mLeft + mPaddingLeft - mTotalLength;
- break;
-
- case Gravity.CENTER_HORIZONTAL:
- childLeft += ((mRight - mLeft) - mTotalLength) / 2;
- break;
- }
+ switch (Gravity.getAbsoluteGravity(majorGravity, isLayoutRtl())) {
+ case Gravity.RIGHT:
+ // mTotalLength contains the padding already
+ childLeft = mPaddingLeft + mRight - mLeft - mTotalLength;
+ break;
+
+ case Gravity.CENTER_HORIZONTAL:
+ // mTotalLength contains the padding already
+ childLeft = mPaddingLeft + (mRight - mLeft - mTotalLength) / 2;
+ break;
+
+ case Gravity.LEFT:
+ default:
+ childLeft = mPaddingLeft;
+ break;
}
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
- if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
- childLeft += mDividerWidth;
+ int start = 0;
+ int dir = 1;
+ //In case of RTL, start drawing from the last child.
+ if (isLayoutRtl) {
+ start = count - 1;
+ dir = -1;
}
for (int i = 0; i < count; i++) {
- final View child = getVirtualChildAt(i);
+ int childIndex = start + dir * i;
+ final View child = getVirtualChildAt(childIndex);
if (child == null) {
- childLeft += measureNullChild(i);
+ childLeft += measureNullChild(childIndex);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
@@ -1523,17 +1593,17 @@ public class LinearLayout extends ViewGroup {
break;
}
+ if (hasDividerBeforeChildAt(childIndex)) {
+ childLeft += mDividerWidth;
+ }
+
childLeft += lp.leftMargin;
setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);
childLeft += childWidth + lp.rightMargin +
getNextLocationOffset(child);
- if (showDividerMiddle) {
- childLeft += mDividerWidth;
- }
-
- i += getChildrenSkipCount(child, i);
+ i += getChildrenSkipCount(child, childIndex);
}
}
}
@@ -1578,8 +1648,8 @@ public class LinearLayout extends ViewGroup {
@android.view.RemotableViewMethod
public void setGravity(int gravity) {
if (mGravity != gravity) {
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
- gravity |= Gravity.LEFT;
+ if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.START;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
@@ -1593,9 +1663,9 @@ public class LinearLayout extends ViewGroup {
@android.view.RemotableViewMethod
public void setHorizontalGravity(int horizontalGravity) {
- final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
- mGravity = (mGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) | gravity;
+ final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+ if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {
+ mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity;
requestLayout();
}
}
@@ -1672,6 +1742,8 @@ public class LinearLayout extends ViewGroup {
@ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
@ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
@ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
+ @ViewDebug.IntToString(from = Gravity.START, to = "START"),
+ @ViewDebug.IntToString(from = Gravity.END, to = "END"),
@ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index af954c9..e7a9e41 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -251,7 +251,7 @@ public class ListView extends AbsListView {
*/
public void addHeaderView(View v, Object data, boolean isSelectable) {
- if (mAdapter != null) {
+ if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
throw new IllegalStateException(
"Cannot add header view to list -- setAdapter has already been called.");
}
@@ -261,6 +261,12 @@ public class ListView extends AbsListView {
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
+
+ // in the case of re-adding a header view, or adding one later on,
+ // we need to notify the observer
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
}
/**
@@ -294,7 +300,9 @@ public class ListView extends AbsListView {
if (mHeaderViewInfos.size() > 0) {
boolean result = false;
if (((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
- mDataSetObserver.onChanged();
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
result = true;
}
removeFixedViewInfo(v, mHeaderViewInfos);
@@ -328,6 +336,12 @@ public class ListView extends AbsListView {
* @param isSelectable true if the footer view can be selected
*/
public void addFooterView(View v, Object data, boolean isSelectable) {
+
+ // NOTE: do not enforce the adapter being null here, since unlike in
+ // addHeaderView, it was never enforced here, and so existing apps are
+ // relying on being able to add a footer and then calling setAdapter to
+ // force creation of the HeaderViewListAdapter wrapper
+
FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
@@ -371,7 +385,9 @@ public class ListView extends AbsListView {
if (mFooterViewInfos.size() > 0) {
boolean result = false;
if (((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
- mDataSetObserver.onChanged();
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
result = true;
}
removeFixedViewInfo(v, mFooterViewInfos);
@@ -1552,7 +1568,7 @@ public class ListView extends AbsListView {
// take focus back to us temporarily to avoid the eventual
// call to clear focus when removing the focused child below
- // from messing things up when ViewRoot assigns focus back
+ // from messing things up when ViewAncestor assigns focus back
// to someone else
final View focusedChild = getFocusedChild();
if (focusedChild != null) {
@@ -1982,36 +1998,28 @@ public class ListView extends AbsListView {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
// If the item count is less than 15 then subtract disabled items from the count and
// position. Otherwise ignore disabled items.
- if (!populated) {
- int itemCount = 0;
- int currentItemIndex = getSelectedItemPosition();
-
- ListAdapter adapter = getAdapter();
- if (adapter != null) {
- final int count = adapter.getCount();
- if (count < 15) {
- for (int i = 0; i < count; i++) {
- if (adapter.isEnabled(i)) {
- itemCount++;
- } else if (i <= currentItemIndex) {
- currentItemIndex--;
- }
- }
- } else {
- itemCount = count;
+ int itemCount = 0;
+ int currentItemIndex = getSelectedItemPosition();
+
+ ListAdapter adapter = getAdapter();
+ if (adapter != null) {
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ if (adapter.isEnabled(i)) {
+ itemCount++;
+ } else if (i <= currentItemIndex) {
+ currentItemIndex--;
}
}
-
- event.setItemCount(itemCount);
- event.setCurrentItemIndex(currentItemIndex);
}
- return populated;
+ event.setItemCount(itemCount);
+ event.setCurrentItemIndex(currentItemIndex);
}
/**
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index 02c1ec7..134e4c4 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -30,7 +30,7 @@ import android.widget.MultiAutoCompleteTextView.Tokenizer;
* can show completion suggestions for the substring of the text where
* the user is typing instead of necessarily for the entire thing.
* <p>
- * You must must provide a {@link Tokenizer} to distinguish the
+ * You must provide a {@link Tokenizer} to distinguish the
* various substrings.
*
* <p>The following code snippet shows how to create a text view which suggests
@@ -41,7 +41,7 @@ import android.widget.MultiAutoCompleteTextView.Tokenizer;
* protected void onCreate(Bundle savedInstanceState) {
* super.onCreate(savedInstanceState);
* setContentView(R.layout.autocomplete_7);
- *
+ *
* ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
* android.R.layout.simple_dropdown_item_1line, COUNTRIES);
* MultiAutoCompleteTextView textView = (MultiAutoCompleteTextView) findViewById(R.id.edit);
@@ -132,7 +132,7 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
* Instead of validating the entire text, this subclass method validates
* each token of the text individually. Empty tokens are removed.
*/
- @Override
+ @Override
public void performValidation() {
Validator v = getValidator();
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index a5b7281..563fc26 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -392,11 +392,11 @@ public class PopupWindow {
mContentView = contentView;
- if (mContext == null) {
+ if (mContext == null && mContentView != null) {
mContext = mContentView.getContext();
}
- if (mWindowManager == null) {
+ if (mWindowManager == null && mContentView != null) {
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
}
@@ -939,7 +939,9 @@ public class PopupWindow {
* @param p the layout parameters of the popup's content view
*/
private void invokePopup(WindowManager.LayoutParams p) {
- p.packageName = mContext.getPackageName();
+ if (mContext != null) {
+ p.packageName = mContext.getPackageName();
+ }
mWindowManager.addView(mPopupView, p);
}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 6b676b4..ed9114a 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -16,6 +16,8 @@
package android.widget;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -41,6 +43,8 @@ import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -49,8 +53,6 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
-import com.android.internal.R;
-
/**
* <p>
@@ -187,6 +189,7 @@ import com.android.internal.R;
public class ProgressBar extends View {
private static final int MAX_LEVEL = 10000;
private static final int ANIMATION_RESOLUTION = 200;
+ private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
int mMinWidth;
int mMaxWidth;
@@ -218,6 +221,8 @@ public class ProgressBar extends View {
private int mAnimationResolution;
+ private AccessibilityEventSender mAccessibilityEventSender;
+
/**
* Create a new progress bar with range 0...100 and initial progress of 0.
* @param context the application environment
@@ -604,8 +609,11 @@ public class ProgressBar extends View {
onProgressRefresh(scale, fromUser);
}
}
-
- void onProgressRefresh(float scale, boolean fromUser) {
+
+ void onProgressRefresh(float scale, boolean fromUser) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ scheduleAccessibilityEventSender();
+ }
}
private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
@@ -908,6 +916,12 @@ public class ProgressBar extends View {
}
@Override
+ public boolean isLayoutRtl(Drawable who) {
+ return (who == mProgressDrawable || who == mIndeterminateDrawable) ?
+ isLayoutRtl() : super.isLayoutRtl(who);
+ }
+
+ @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateDrawableBounds(w, h);
}
@@ -1069,8 +1083,46 @@ public class ProgressBar extends View {
if (mIndeterminate) {
stopAnimation();
}
+ if(mRefreshProgressRunnable != null) {
+ removeCallbacks(mRefreshProgressRunnable);
+ }
+ if (mAccessibilityEventSender != null) {
+ removeCallbacks(mAccessibilityEventSender);
+ }
// This should come after stopAnimation(), otherwise an invalidate message remains in the
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
super.onDetachedFromWindow();
}
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setItemCount(mMax);
+ event.setCurrentItemIndex(mProgress);
+ }
+
+ /**
+ * Schedule a command for sending an accessibility event.
+ * </br>
+ * Note: A command is used to ensure that accessibility events
+ * are sent at most one in a given time frame to save
+ * system resources while the progress changes quickly.
+ */
+ private void scheduleAccessibilityEventSender() {
+ if (mAccessibilityEventSender == null) {
+ mAccessibilityEventSender = new AccessibilityEventSender();
+ } else {
+ removeCallbacks(mAccessibilityEventSender);
+ }
+ postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
+ }
+
+ /**
+ * Command for sending an accessibility event.
+ */
+ private class AccessibilityEventSender implements Runnable {
+ public void run() {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+ }
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index a47359f..a4771d5 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -18,17 +18,23 @@ package android.widget;
import com.android.internal.R;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
import android.content.Context;
-import android.content.res.TypedArray;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.util.Poolable;
import android.util.Pool;
-import android.util.Pools;
+import android.util.Poolable;
import android.util.PoolableManager;
-import static android.util.Log.d;
+import android.util.Pools;
+import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
@@ -36,12 +42,7 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.RemoteViews.RemoteView;
-import java.util.Comparator;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.LinkedList;
-import java.util.HashSet;
-import java.util.ArrayList;
+import static android.util.Log.d;
/**
* A Layout where the positions of the children can be described in relation to each other or to the
@@ -186,6 +187,11 @@ public class RelativeLayout extends ViewGroup {
a.recycle();
}
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
/**
* Defines which View is ignored when the gravity is applied. This setting has no
* effect if the gravity is <code>Gravity.LEFT | Gravity.TOP</code>.
@@ -216,8 +222,8 @@ public class RelativeLayout extends ViewGroup {
@android.view.RemotableViewMethod
public void setGravity(int gravity) {
if (mGravity != gravity) {
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
- gravity |= Gravity.LEFT;
+ if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.START;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
@@ -231,9 +237,9 @@ public class RelativeLayout extends ViewGroup {
@android.view.RemotableViewMethod
public void setHorizontalGravity(int horizontalGravity) {
- final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
- mGravity = (mGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) | gravity;
+ final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+ if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {
+ mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity;
requestLayout();
}
}
@@ -334,7 +340,7 @@ public class RelativeLayout extends ViewGroup {
mHasBaselineAlignedChild = false;
View ignore = null;
- int gravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final boolean horizontalGravity = gravity != Gravity.LEFT && gravity != 0;
gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
@@ -489,7 +495,8 @@ public class RelativeLayout extends ViewGroup {
height - mPaddingBottom);
final Rect contentBounds = mContentBounds;
- Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds);
+ Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
+ isLayoutRtl());
final int horizontalOffset = contentBounds.left - left;
final int verticalOffset = contentBounds.top - top;
@@ -1434,6 +1441,7 @@ public class RelativeLayout extends ViewGroup {
);
private Node mNext;
+ private boolean mIsPooled;
public void setNextPoolable(Node element) {
mNext = element;
@@ -1443,6 +1451,14 @@ public class RelativeLayout extends ViewGroup {
return mNext;
}
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
+
static Node acquire(View view) {
final Node node = sPool.acquire();
node.view = view;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c854fac..9cf2718 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -125,7 +125,7 @@ public class RemoteViews implements Parcelable, Filter {
* SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
*/
private abstract static class Action implements Parcelable {
- public abstract void apply(View root) throws ActionException;
+ public abstract void apply(View root, ViewGroup rootParent) throws ActionException;
public int describeContents() {
return 0;
@@ -183,7 +183,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View view = root.findViewById(viewId);
if (!(view instanceof AdapterView<?>)) return;
@@ -214,7 +214,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -295,7 +295,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -360,6 +360,60 @@ public class RemoteViews implements Parcelable, Filter {
public final static int TAG = 8;
}
+ private class SetRemoteViewsAdapterIntent extends Action {
+ public SetRemoteViewsAdapterIntent(int id, Intent intent) {
+ this.viewId = id;
+ this.intent = intent;
+ }
+
+ public SetRemoteViewsAdapterIntent(Parcel parcel) {
+ viewId = parcel.readInt();
+ intent = Intent.CREATOR.createFromParcel(parcel);
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ intent.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent) {
+ 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("RemoteViews", "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 " +
+ "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
+ return;
+ }
+
+ // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
+ // RemoteViewsService
+ AppWidgetHostView host = (AppWidgetHostView) rootParent;
+ intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
+ if (target instanceof AbsListView) {
+ AbsListView v = (AbsListView) target;
+ v.setRemoteViewsAdapter(intent);
+ } else if (target instanceof AdapterViewAnimator) {
+ AdapterViewAnimator v = (AdapterViewAnimator) target;
+ v.setRemoteViewsAdapter(intent);
+ }
+ }
+
+ int viewId;
+ Intent intent;
+
+ public final static int TAG = 10;
+ }
+
/**
* Equivalent to calling
* {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
@@ -383,7 +437,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -479,7 +533,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -539,7 +593,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View view = root.findViewById(viewId);
if (view == null) return;
@@ -755,7 +809,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View view = root.findViewById(viewId);
if (view == null) return;
@@ -850,7 +904,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final Context context = root.getContext();
final ViewGroup target = (ViewGroup) root.findViewById(viewId);
if (target == null) return;
@@ -952,6 +1006,9 @@ public class RemoteViews implements Parcelable, Filter {
case SetOnClickFillInIntent.TAG:
mActions.add(new SetOnClickFillInIntent(parcel));
break;
+ case SetRemoteViewsAdapterIntent.TAG:
+ mActions.add(new SetRemoteViewsAdapterIntent(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -1287,16 +1344,29 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
*
- * @param appWidgetId The id of the app widget which contains the specified view
+ * @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 view whose text should change
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
+ * @deprecated This method has been deprecated. See
+ * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
*/
+ @Deprecated
public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
- // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
- // RemoteViewsService
- intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, appWidgetId);
- setIntent(viewId, "setRemoteViewsAdapter", intent);
+ setRemoteAdapter(viewId, intent);
+ }
+
+ /**
+ * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
+ * Can only be used for App Widgets.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param intent The intent of the service which will be
+ * providing data to the RemoteViewsAdapter
+ */
+ public void setRemoteAdapter(int viewId, Intent intent) {
+ addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
}
/**
@@ -1499,7 +1569,7 @@ public class RemoteViews implements Parcelable, Filter {
result = inflater.inflate(mLayoutId, parent, false);
- performApply(result);
+ performApply(result, parent);
return result;
}
@@ -1514,15 +1584,15 @@ public class RemoteViews implements Parcelable, Filter {
*/
public void reapply(Context context, View v) {
prepareContext(context);
- performApply(v);
+ performApply(v, (ViewGroup) v.getParent());
}
- private void performApply(View v) {
+ private void performApply(View v, ViewGroup parent) {
if (mActions != null) {
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
- a.apply(v);
+ a.apply(v, parent);
}
}
}
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 1c0a2bb..40b0a9c 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -29,6 +29,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -156,13 +157,16 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// create in response to this bind
factory.onDataSetChanged();
}
- } catch (Exception e) {
+ } catch (RemoteException e) {
Log.e(TAG, "Error notifying factory of data set changed in " +
"onServiceConnected(): " + e.getMessage());
// Return early to prevent anything further from being notified
// (effectively nothing has changed)
return;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error notifying factory of data set changed in " +
+ "onServiceConnected(): " + e.getMessage());
}
// Request meta data so that we have up to date data when calling back to
@@ -777,7 +781,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
tmpMetaData.count = count;
tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
}
- } catch (Exception e) {
+ } catch(RemoteException e) {
+ processException("updateMetaData", e);
+ } catch(RuntimeException e) {
processException("updateMetaData", e);
}
}
@@ -792,12 +798,15 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
- } catch (Exception e) {
+ } catch (RemoteException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
+ return;
}
if (remoteViews == null) {
@@ -971,18 +980,20 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
return getCount() <= 0;
}
-
private void onNotifyDataSetChanged() {
// Complete the actual notifyDataSetChanged() call initiated earlier
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
try {
factory.onDataSetChanged();
- } catch (Exception e) {
+ } catch (RemoteException e) {
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
// Return early to prevent from further being notified (since nothing has
// changed)
return;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+ return;
}
// Flush the cache so that we can reload new items from the service
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index e0b08d4..7ba4777 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -138,34 +138,87 @@ public abstract class RemoteViewsService extends Service {
return mIsCreated;
}
public synchronized void onDataSetChanged() {
- mFactory.onDataSetChanged();
+ try {
+ mFactory.onDataSetChanged();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
}
public synchronized int getCount() {
- return mFactory.getCount();
+ int count = 0;
+ try {
+ count = mFactory.getCount();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return count;
}
public synchronized RemoteViews getViewAt(int position) {
- RemoteViews rv = mFactory.getViewAt(position);
- rv.setIsWidgetCollectionChild(true);
+ RemoteViews rv = null;
+ try {
+ rv = mFactory.getViewAt(position);
+ if (rv != null) {
+ rv.setIsWidgetCollectionChild(true);
+ }
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
return rv;
}
public synchronized RemoteViews getLoadingView() {
- return mFactory.getLoadingView();
+ RemoteViews rv = null;
+ try {
+ rv = mFactory.getLoadingView();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return rv;
}
public synchronized int getViewTypeCount() {
- return mFactory.getViewTypeCount();
+ int count = 0;
+ try {
+ count = mFactory.getViewTypeCount();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return count;
}
public synchronized long getItemId(int position) {
- return mFactory.getItemId(position);
+ long id = 0;
+ try {
+ id = mFactory.getItemId(position);
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return id;
}
public synchronized boolean hasStableIds() {
- return mFactory.hasStableIds();
+ boolean hasStableIds = false;
+ try {
+ hasStableIds = mFactory.hasStableIds();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return hasStableIds;
}
public void onDestroy(Intent intent) {
synchronized (sLock) {
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
if (RemoteViewsService.sRemoteViewFactories.containsKey(fc)) {
RemoteViewsFactory factory = RemoteViewsService.sRemoteViewFactories.get(fc);
- factory.onDestroy();
+ try {
+ factory.onDestroy();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
RemoteViewsService.sRemoteViewFactories.remove(fc);
}
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index ade3a0a..27edb88 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -162,6 +162,11 @@ public class ScrollView extends FrameLayout {
}
@Override
+ public boolean shouldDelayChildPressedState() {
+ return true;
+ }
+
+ @Override
protected float getTopFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 9933d68..586ece8 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -93,6 +93,7 @@ public class SearchView extends LinearLayout {
private boolean mClearingFocus;
private int mMaxWidth;
private boolean mVoiceButtonEnabled;
+ private CharSequence mUserQuery;
private SearchableInfo mSearchable;
private Bundle mAppSearchData;
@@ -372,6 +373,7 @@ public class SearchView extends LinearLayout {
mQueryTextView.setText(query);
if (query != null) {
mQueryTextView.setSelection(query.length());
+ mUserQuery = query;
}
// If the query is not empty and submit is requested, submit the query
@@ -885,6 +887,7 @@ public class SearchView extends LinearLayout {
private void onTextChanged(CharSequence newText) {
CharSequence text = mQueryTextView.getText();
+ mUserQuery = text;
boolean hasText = !TextUtils.isEmpty(text);
if (isSubmitButtonEnabled()) {
updateSubmitButton(hasText);
@@ -1124,7 +1127,7 @@ public class SearchView extends LinearLayout {
if (data != null) {
intent.setData(data);
}
- intent.putExtra(SearchManager.USER_QUERY, query);
+ intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
if (query != null) {
intent.putExtra(SearchManager.QUERY, query);
}
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index 3d2a252..c5c6c69 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -338,6 +338,12 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
@Override
public Cursor swapCursor(Cursor c) {
+ // super.swapCursor() will notify observers before we have
+ // a valid mapping, make sure we have a mapping before this
+ // happens
+ if (mFrom == null) {
+ findColumns(mOriginalFrom);
+ }
Cursor res = super.swapCursor(c);
// rescan columns in case cursor layout is different
findColumns(mOriginalFrom);
@@ -358,7 +364,13 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
mOriginalFrom = from;
mTo = to;
- super.changeCursor(c);
+ // super.changeCursor() will notify observers before we have
+ // a valid mapping, make sure we have a mapping before this
+ // happens
+ if (mFrom == null) {
+ findColumns(mOriginalFrom);
+ }
+ super.changeCursor(c);
findColumns(mOriginalFrom);
}
diff --git a/core/java/android/widget/Space.java b/core/java/android/widget/Space.java
new file mode 100644
index 0000000..d98b937
--- /dev/null
+++ b/core/java/android/widget/Space.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Space is a lightweight View subclass that may be used to create gaps between components
+ * in general purpose layouts.
+ */
+public final class Space extends View {
+ /**
+ * {@inheritDoc}
+ */
+ public Space(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Space(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Space(Context context) {
+ super(context);
+ }
+
+ /**
+ * Draw nothing.
+ *
+ * @param canvas an unused parameter.
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ViewGroup.LayoutParams getLayoutParams() {
+ return super.getLayoutParams();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ super.setLayoutParams(params);
+ }
+}
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 21c61bd..c4ba7c8 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -20,6 +20,7 @@ import java.lang.ref.WeakReference;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -115,7 +116,7 @@ public class StackView extends AdapterViewAnimator {
private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000;
- private static long MIN_TIME_BETWEEN_SCROLLS = 100;
+ private static final long MIN_TIME_BETWEEN_SCROLLS = 100;
/**
* These variables are all related to the current state of touch interaction
@@ -132,6 +133,8 @@ public class StackView extends AdapterViewAnimator {
private int mMaximumVelocity;
private VelocityTracker mVelocityTracker;
private boolean mTransitionIsSetup = false;
+ private int mResOutColor;
+ private int mClickColor;
private static HolographicHelper sHolographicHelper;
private ImageView mHighlight;
@@ -146,12 +149,24 @@ public class StackView extends AdapterViewAnimator {
private final Rect stackInvalidateRect = new Rect();
public StackView(Context context) {
- super(context);
- initStackView();
+ this(context, null);
}
public StackView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, com.android.internal.R.attr.stackViewStyle);
+ }
+
+ public StackView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.StackView, defStyleAttr, 0);
+
+ mResOutColor = a.getColor(
+ com.android.internal.R.styleable.StackView_resOutColor, 0);
+ mClickColor = a.getColor(
+ com.android.internal.R.styleable.StackView_clickColor, 0);
+
+ a.recycle();
initStackView();
}
@@ -198,8 +213,7 @@ public class StackView extends AdapterViewAnimator {
* Animate the views between different relative indexes within the {@link AdapterViewAnimator}
*/
void transformViewForTransition(int fromIndex, int toIndex, final View view, boolean animate) {
- ObjectAnimator alphaOa = null;
- ObjectAnimator oldAlphaOa = null;
+ ObjectAnimator alphaOa;
if (!animate) {
((StackFrame) view).cancelSliderAnimator();
@@ -357,7 +371,7 @@ public class StackView extends AdapterViewAnimator {
private void setupStackSlider(View v, int mode) {
mStackSlider.setMode(mode);
if (v != null) {
- mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
+ mHighlight.setImageBitmap(sHolographicHelper.createResOutline(v, mResOutColor));
mHighlight.setRotation(v.getRotation());
mHighlight.setTranslationY(v.getTranslationY());
mHighlight.setTranslationX(v.getTranslationX());
@@ -412,8 +426,8 @@ public class StackView extends AdapterViewAnimator {
// Here we need to make sure that the z-order of the children is correct
for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
int index = modulo(i, getWindowSize());
- ViewAndIndex vi = mViewsMap.get(index);
- if (vi != null) {
+ ViewAndMetaData vm = mViewsMap.get(index);
+ if (vm != null) {
View v = mViewsMap.get(index).view;
if (v != null) v.bringToFront();
}
@@ -429,8 +443,8 @@ public class StackView extends AdapterViewAnimator {
if (!mClickFeedbackIsValid) {
View v = getViewAtRelativeIndex(1);
if (v != null) {
- mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v,
- HolographicHelper.CLICK_FEEDBACK));
+ mClickFeedback.setImageBitmap(
+ sHolographicHelper.createClickOutline(v, mClickColor));
mClickFeedback.setTranslationX(v.getTranslationX());
mClickFeedback.setTranslationY(v.getTranslationY());
}
@@ -1261,13 +1275,11 @@ public class StackView extends AdapterViewAnimator {
boolean firstPass = true;
parentRect.set(0, 0, 0, 0);
- int depth = 0;
while (p.getParent() != null && p.getParent() instanceof View
&& !parentRect.contains(globalInvalidateRect)) {
if (!firstPass) {
globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
- p.getScrollY());
- depth++;
}
firstPass = false;
p = (View) p.getParent();
@@ -1355,16 +1367,19 @@ public class StackView extends AdapterViewAnimator {
mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL);
}
- Bitmap createOutline(View v) {
- return createOutline(v, RES_OUT);
+ Bitmap createClickOutline(View v, int color) {
+ return createOutline(v, CLICK_FEEDBACK, color);
+ }
+
+ Bitmap createResOutline(View v, int color) {
+ return createOutline(v, RES_OUT, color);
}
- Bitmap createOutline(View v, int type) {
+ Bitmap createOutline(View v, int type, int color) {
+ mHolographicPaint.setColor(color);
if (type == RES_OUT) {
- mHolographicPaint.setColor(0xff6699ff);
mBlurPaint.setMaskFilter(mSmallBlurMaskFilter);
} else if (type == CLICK_FEEDBACK) {
- mHolographicPaint.setColor(0x886699ff);
mBlurPaint.setMaskFilter(mLargeBlurMaskFilter);
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index cd4b732..5c6a26f 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -37,6 +35,8 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
+import com.android.internal.R;
+
/**
* A Switch is a two-state toggle switch widget that can select between two
* options. The user may drag the "thumb" back and forth to choose the selected option,
@@ -84,6 +84,7 @@ public class Switch extends CompoundButton {
private Layout mOnLayout;
private Layout mOffLayout;
+ @SuppressWarnings("hiding")
private final Rect mTempRect = new Rect();
private static final int[] CHECKED_STATE_SET = {
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 6f76dd0..1fe1f79 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -427,12 +427,19 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- event.setItemCount(getTabCount());
- event.setCurrentItemIndex(mSelectedTab);
+ onPopulateAccessibilityEvent(event);
+ // Dispatch only to the selected tab.
if (mSelectedTab != -1) {
- getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+ return getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
}
- return true;
+ return false;
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setItemCount(getTabCount());
+ event.setCurrentItemIndex(mSelectedTab);
}
/**
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index b612004..5f20c85 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -224,7 +224,8 @@ public class TableRow extends LinearLayout {
final int childWidth = child.getMeasuredWidth();
lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth;
- switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
// don't offset on X axis
break;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 435cf4e..9a5977a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -16,11 +16,6 @@
package android.widget;
-import com.android.internal.util.FastMath;
-import com.android.internal.widget.EditableInputConnection;
-
-import org.xmlpull.v1.XmlPullParserException;
-
import android.R;
import android.content.ClipData;
import android.content.ClipData.Item;
@@ -60,6 +55,7 @@ import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
@@ -80,12 +76,17 @@ import android.text.method.SingleLineTransformationMethod;
import android.text.method.TextKeyListener;
import android.text.method.TimeKeyListener;
import android.text.method.TransformationMethod;
+import android.text.method.WordIterator;
import android.text.style.ClickableSpan;
import android.text.style.ParagraphStyle;
+import android.text.style.SuggestionSpan;
+import android.text.style.TextAppearanceSpan;
import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.util.Log;
import android.util.TypedValue;
@@ -102,16 +103,17 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewAncestor;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
-import android.view.ViewRoot;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
@@ -123,8 +125,14 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.util.FastMath;
+import com.android.internal.widget.EditableInputConnection;
+
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.lang.ref.WeakReference;
+import java.text.BreakIterator;
import java.util.ArrayList;
/**
@@ -309,6 +317,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mTextEditPasteWindowLayout, mTextEditSidePasteWindowLayout;
private int mTextEditNoPasteWindowLayout, mTextEditSideNoPasteWindowLayout;
+ private int mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout;
+ private int mTextEditSuggestionItemLayout;
+ private SuggestionsPopupWindow mSuggestionsPopupWindow;
+ private SuggestionRangeSpan mSuggestionRangeSpan;
+ private boolean mSuggestionsEnabled = true;
+
private int mCursorDrawableRes;
private final Drawable[] mCursorDrawable = new Drawable[2];
private int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2
@@ -317,13 +331,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private Drawable mSelectHandleRight;
private Drawable mSelectHandleCenter;
- private int mLastDownPositionX, mLastDownPositionY;
+ private float mLastDownPositionX, mLastDownPositionY;
private Callback mCustomSelectionActionModeCallback;
private final int mSquaredTouchSlopDistance;
// Set when this TextView gained focus with some text selected. Will start selection mode.
private boolean mCreatedWithASelection = false;
+ private WordIterator mWordIterator;
+
/*
* Kick-start the font cache for the zygote process (to pay the cost of
* initializing freetype for our default font only once).
@@ -777,9 +793,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mTextEditSideNoPasteWindowLayout = a.getResourceId(attr, 0);
break;
+ case com.android.internal.R.styleable.TextView_textEditSuggestionsBottomWindowLayout:
+ mTextEditSuggestionsBottomWindowLayout = a.getResourceId(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextView_textEditSuggestionsTopWindowLayout:
+ mTextEditSuggestionsTopWindowLayout = a.getResourceId(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
+ mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
+ break;
+
case com.android.internal.R.styleable.TextView_textIsSelectable:
mTextIsSelectable = a.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextView_suggestionsEnabled:
+ mSuggestionsEnabled = a.getBoolean(attr, true);
+ break;
}
}
a.recycle();
@@ -2062,8 +2094,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_gravity
*/
public void setGravity(int gravity) {
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
- gravity |= Gravity.LEFT;
+ if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.START;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
gravity |= Gravity.TOP;
@@ -2071,8 +2103,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean newLayout = false;
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
- (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
+ if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
+ (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
newLayout = true;
}
@@ -2534,6 +2566,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sp.removeSpan(cw);
}
+ // hideControllers would do it, but it gets called after this method on rotation
+ sp.removeSpan(mSuggestionRangeSpan);
+
ss.text = sp;
} else {
ss.text = mText.toString();
@@ -2872,8 +2907,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setText(mCharWrapper, mBufferType, false, oldlen);
}
- private static class CharWrapper
- implements CharSequence, GetChars, GraphicsOperations {
+ private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
private char[] mChars;
private int mStart, mLength;
@@ -2949,6 +2983,16 @@ 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;
@@ -3337,13 +3381,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Handler h = getHandler();
if (h != null) {
long eventTime = SystemClock.uptimeMillis();
- h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ h.sendMessage(h.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
| KeyEvent.FLAG_EDITOR_ACTION)));
- h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ h.sendMessage(h.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
new KeyEvent(SystemClock.uptimeMillis(), eventTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
@@ -3977,13 +4021,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
observer.removeOnPreDrawListener(this);
mPreDrawState = PREDRAW_NOT_REGISTERED;
}
- // No need to create the controller, as getXXController would.
- if (mInsertionPointCursorController != null) {
- observer.removeOnTouchModeChangeListener(mInsertionPointCursorController);
- }
- if (mSelectionModifierCursorController != null) {
- observer.removeOnTouchModeChangeListener(mSelectionModifierCursorController);
- }
if (mError != null) {
hideError();
@@ -4109,6 +4146,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
+ public boolean isLayoutRtl(Drawable who) {
+ if (who == null) return false;
+ if (mDrawables != null) {
+ final Drawables drawables = mDrawables;
+ if (who == drawables.mDrawableLeft || who == drawables.mDrawableRight ||
+ who == drawables.mDrawableTop || who == drawables.mDrawableBottom) {
+ return isLayoutRtl();
+ }
+ }
+ return super.isLayoutRtl(who);
+ }
+
+ @Override
protected boolean onSetAlpha(int alpha) {
// Alpha is supported if and only if the drawing can be done in one pass.
// TODO text with spans with a background color currently do not respect this alpha.
@@ -4210,6 +4260,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onDraw(Canvas canvas) {
+ if (mPreDrawState == PREDRAW_DONE) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnPreDrawListener(this);
+ mPreDrawState = PREDRAW_NOT_REGISTERED;
+ }
+
if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return;
restartMarqueeIfNeeded();
@@ -4281,12 +4337,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (mPreDrawState == PREDRAW_DONE) {
- final ViewTreeObserver observer = getViewTreeObserver();
- observer.removeOnPreDrawListener(this);
- mPreDrawState = PREDRAW_NOT_REGISTERED;
- }
-
int color = mCurTextColor;
if (mLayout == null) {
@@ -4347,9 +4397,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
}
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
- (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
+ (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
}
@@ -4549,15 +4600,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (translate) canvas.translate(0, -cursorOffsetVertical);
}
- /**
- * Update the positions of the CursorControllers. Needed by WebTextView,
- * which does not draw.
- * @hide
- */
- protected void updateCursorControllerPositions() {
- // TODO remove
- }
-
@Override
public void getFocusedRect(Rect r) {
if (mLayout == null) {
@@ -5236,7 +5278,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param text The auto complete text the user has selected.
*/
public void onCommitCompletion(CompletionInfo text) {
- // Intentionally empty
+ // intentionally empty
}
/**
@@ -5423,6 +5465,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* of edit operations through a call to link {@link #beginBatchEdit()}.
*/
public void onBeginBatchEdit() {
+ // intentionally empty
}
/**
@@ -5430,6 +5473,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* of edit operations through a call to link {@link #endBatchEdit}.
*/
public void onEndBatchEdit() {
+ // intentionally empty
}
/**
@@ -5502,7 +5546,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
Layout.Alignment alignment;
- switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
alignment = Layout.Alignment.ALIGN_CENTER;
break;
@@ -6276,15 +6321,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (isFocused()) {
- // This offsets because getInterestingRect() is in terms of
- // viewport coordinates, but requestRectangleOnScreen()
- // is in terms of content coordinates.
+ // This offsets because getInterestingRect() is in terms of viewport coordinates, but
+ // requestRectangleOnScreen() is in terms of content coordinates.
- Rect r = new Rect(x, top, x + 1, bottom);
- getInterestingRect(r, line);
- r.offset(mScrollX, mScrollY);
+ if (mTempRect == null) mTempRect = new Rect();
+ mTempRect.set(x, top, x + 1, bottom);
+ getInterestingRect(mTempRect, line);
+ mTempRect.offset(mScrollX, mScrollY);
- if (requestRectangleOnScreen(r)) {
+ if (requestRectangleOnScreen(mTempRect)) {
changed = true;
}
}
@@ -6757,25 +6802,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * This method is called when the text is changed, in case any
- * subclasses would like to know.
+ * This method is called when the text is changed, in case any subclasses
+ * would like to know.
*
- * @param text The text the TextView is displaying.
- * @param start The offset of the start of the range of the text
- * that was modified.
- * @param before The offset of the former end of the range of the
- * text that was modified. If text was simply inserted,
- * this will be the same as <code>start</code>.
- * If text was replaced with new text or deleted, the
- * length of the old text was <code>before-start</code>.
- * @param after The offset of the end of the range of the text
- * that was modified. If text was simply deleted,
- * this will be the same as <code>start</code>.
- * If text was replaced with new text or inserted,
- * the length of the new text is <code>after-start</code>.
+ * Within <code>text</code>, the <code>lengthAfter</code> characters
+ * beginning at <code>start</code> have just replaced old text that had
+ * length <code>lengthBefore</code>. It is an error to attempt to make
+ * changes to <code>text</code> from this callback.
+ *
+ * @param text The text the TextView is displaying
+ * @param start The offset of the start of the range of the text that was
+ * modified
+ * @param lengthBefore The length of the former text that has been replaced
+ * @param lengthAfter The length of the replacement modified text
*/
- protected void onTextChanged(CharSequence text,
- int start, int before, int after) {
+ protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+ // intentionally empty
}
/**
@@ -6786,6 +6828,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param selEnd The new selection end location.
*/
protected void onSelectionChanged(int selStart, int selEnd) {
+ // intentionally empty
}
/**
@@ -7132,7 +7175,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// The DecorView does not have focus when the 'Done' ExtractEditText button is
- // pressed. Since it is the ViewRoot's mView, it requests focus before
+ // pressed. Since it is the ViewAncestor's mView, it requests focus before
// ExtractEditText clears focus, which gives focus to the ExtractEditText.
// This special case ensure that we keep current selection in that case.
// It would be better to know why the DecorView does not have focus at that time.
@@ -7201,14 +7244,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
-
- // Performed after super.onFocusChanged so that this TextView is registered and can ask for
- // the IME. Showing the IME while focus is moved using the D-Pad is a bad idea, however this
- // does not happen in that case (using the arrows on a bluetooth keyboard).
- if (focused && isTextEditable()) {
- final InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) imm.showSoftInput(this, 0);
- }
}
private int getLastTapPosition() {
@@ -7248,11 +7283,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mInputContentType.enterDown = false;
}
hideControllers();
+ removeAllSuggestionSpans();
}
startStopMarquee(hasWindowFocus);
}
+ private void removeAllSuggestionSpans() {
+ if (mText instanceof Editable) {
+ Editable editable = ((Editable) mText);
+ SuggestionSpan[] spans = editable.getSpans(0, mText.length(), SuggestionSpan.class);
+ final int length = spans.length;
+ for (int i = 0; i < length; i++) {
+ editable.removeSpan(spans[i]);
+ }
+ }
+ }
+
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -7299,8 +7346,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (action == MotionEvent.ACTION_DOWN) {
- mLastDownPositionX = (int) event.getX();
- mLastDownPositionY = (int) event.getY();
+ mLastDownPositionX = event.getX();
+ mLastDownPositionY = event.getY();
// Reset this state; it will be re-set if super.onTouchEvent
// causes focus to move to the view.
@@ -7327,9 +7374,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
- final int oldScrollX = mScrollX;
- final int oldScrollY = mScrollY;
-
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
@@ -7346,27 +7390,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (isTextEditable() || mTextIsSelectable) {
- if (mScrollX != oldScrollX || mScrollY != oldScrollY) { // TODO remove
- // Hide insertion anchor while scrolling. Leave selection.
- hideInsertionPointCursorController(); // TODO any motion should hide it
+ if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
+ // Show the IME, except when selecting in read-only text.
+ if (!mTextIsSelectable) {
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ handled |= imm != null && imm.showSoftInput(this, 0);
}
- if (touchIsFinished) {
- // Show the IME, except when selecting in read-only text.
- if (!mTextIsSelectable) {
- final InputMethodManager imm = InputMethodManager.peekInstance();
- handled |= imm != null && imm.showSoftInput(this, 0);
- }
-
- boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
- if (!selectAllGotFocus && hasSelection()) {
- startSelectionActionMode();
- } else {
- stopSelectionActionMode();
- if (hasInsertionController() && !selectAllGotFocus && mText.length() > 0) {
- getInsertionController().show();
- }
+ boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
+ if (!selectAllGotFocus && hasSelection()) {
+ startSelectionActionMode();
+ } else {
+ stopSelectionActionMode();
+ hideSuggestions();
+ if (hasInsertionController() && !selectAllGotFocus && mText.length() > 0) {
+ getInsertionController().show();
}
}
}
@@ -7544,7 +7582,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return 0.0f;
}
} else if (getLineCount() == 1) {
- switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
return 0.0f;
case Gravity.RIGHT:
@@ -7567,7 +7606,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final Marquee marquee = mMarquee;
return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
} else if (getLineCount() == 1) {
- switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
getCompoundPaddingRight();
@@ -7608,7 +7648,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected int computeVerticalScrollExtent() {
return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
}
-
+
+ @Override
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
+ CharSequence thisText = getText();
+ if (TextUtils.isEmpty(thisText)) {
+ return;
+ }
+ if (thisText.toString().toLowerCase().contains(text)) {
+ outViews.add(this);
+ }
+ }
+
public enum BufferType {
NORMAL, SPANNABLE, EDITABLE,
}
@@ -7743,88 +7794,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
hasPrimaryClip());
}
- private boolean isWordCharacter(int c, int type) {
- return (c == '\'' || c == '"' ||
- type == Character.UPPERCASE_LETTER ||
- type == Character.LOWERCASE_LETTER ||
- type == Character.TITLECASE_LETTER ||
- type == Character.MODIFIER_LETTER ||
- type == Character.OTHER_LETTER || // Should handle asian characters
- type == Character.DECIMAL_DIGIT_NUMBER);
- }
-
- /**
- * Returns the offsets delimiting the 'word' located at position offset.
- *
- * @param offset An offset in the text.
- * @return The offsets for the start and end of the word located at <code>offset</code>.
- * The two ints offsets are packed in a long using {@link #packRangeInLong(int, int)}.
- * Returns -1 if no valid word was found.
- */
- private long getWordLimitsAt(int offset) {
- int klass = mInputType & InputType.TYPE_MASK_CLASS;
- int variation = mInputType & InputType.TYPE_MASK_VARIATION;
-
- // Text selection is not permitted in password fields
- if (hasPasswordTransformationMethod()) {
- return -1;
- }
-
- final int len = mText.length();
-
- // Specific text fields: always select the entire text
- if (klass == InputType.TYPE_CLASS_NUMBER ||
- klass == InputType.TYPE_CLASS_PHONE ||
- klass == InputType.TYPE_CLASS_DATETIME ||
- variation == InputType.TYPE_TEXT_VARIATION_URI ||
- variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
- variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
- variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
- return len > 0 ? packRangeInLong(0, len) : -1;
- }
-
- int end = Math.min(offset, len);
- if (end < 0) {
- return -1;
- }
-
- final int MAX_LENGTH = 48;
- int start = end;
-
- for (; start > 0; start--) {
- final char c = mTransformed.charAt(start - 1);
- final int type = Character.getType(c);
- if (start == end && type == Character.OTHER_PUNCTUATION) {
- // Cases where the text ends with a '.' and we select from the end of the line
- // (right after the dot), or when we select from the space character in "aaa, bbb".
- continue;
- }
- if (type == Character.SURROGATE) { // Two Character codepoint
- end = start - 1; // Recheck as a pair when scanning forward
- continue;
- }
- if (!isWordCharacter(c, type)) break;
- if ((end - start) > MAX_LENGTH) return -1;
- }
-
- for (; end < len; end++) {
- final int c = Character.codePointAt(mTransformed, end);
- final int type = Character.getType(c);
- if (!isWordCharacter(c, type)) break;
- if ((end - start) > MAX_LENGTH) return -1;
- if (c > 0xFFFF) { // Two Character codepoint
- end++;
- }
- }
-
- if (start == end) {
- return -1;
- }
-
- // Two ints packed in a long
- return packRangeInLong(start, end);
- }
-
private static long packRangeInLong(int start, int end) {
return (((long) start) << 32) | end;
}
@@ -7837,21 +7806,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return (int) (range & 0x00000000FFFFFFFFL);
}
- private void selectAll() {
- Selection.setSelection((Spannable) mText, 0, mText.length());
+ private boolean selectAll() {
+ final int length = mText.length();
+ Selection.setSelection((Spannable) mText, 0, length);
+ return length > 0;
}
- private void selectCurrentWord() {
+ /**
+ * Adjusts selection to the word under last touch offset.
+ * Return true if the operation was successfully performed.
+ */
+ private boolean selectCurrentWord() {
if (!canSelectText()) {
- return;
+ return false;
}
if (hasPasswordTransformationMethod()) {
// Always select all on a password field.
// Cut/copy menu entries are not available for passwords, but being able to select all
// is however useful to delete or paste to replace the entire content.
- selectAll();
- return;
+ return selectAll();
+ }
+
+ int klass = mInputType & InputType.TYPE_MASK_CLASS;
+ int variation = mInputType & InputType.TYPE_MASK_VARIATION;
+
+ // Specific text field types: select the entire text for these
+ if (klass == InputType.TYPE_CLASS_NUMBER ||
+ klass == InputType.TYPE_CLASS_PHONE ||
+ klass == InputType.TYPE_CLASS_DATETIME ||
+ variation == InputType.TYPE_TEXT_VARIATION_URI ||
+ variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
+ variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
+ variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ return selectAll();
}
long lastTouchOffsets = getLastTouchOffsets();
@@ -7867,22 +7855,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
selectionStart = ((Spanned) mText).getSpanStart(url);
selectionEnd = ((Spanned) mText).getSpanEnd(url);
} else {
- long wordLimits = getWordLimitsAt(minOffset);
- if (wordLimits >= 0) {
- selectionStart = extractRangeStartFromLong(wordLimits);
- } else {
- selectionStart = Math.max(minOffset - 5, 0);
+ if (mWordIterator == null) {
+ mWordIterator = new WordIterator();
}
+ // WordIerator handles text changes, this is a no-op if text in unchanged.
+ mWordIterator.setCharSequence(mText);
- wordLimits = getWordLimitsAt(maxOffset);
- if (wordLimits >= 0) {
- selectionEnd = extractRangeEndFromLong(wordLimits);
- } else {
- selectionEnd = Math.min(maxOffset + 5, mText.length());
- }
+ selectionStart = mWordIterator.getBeginning(minOffset);
+ if (selectionStart == BreakIterator.DONE) return false;
+
+ selectionEnd = mWordIterator.getEnd(maxOffset);
+ if (selectionEnd == BreakIterator.DONE) return false;
}
Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+ return true;
}
private long getLastTouchOffsets() {
@@ -7901,28 +7888,38 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (!isShown()) {
- return false;
- }
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
final boolean isPassword = hasPasswordTransformationMethod();
-
if (!isPassword) {
CharSequence text = getText();
if (TextUtils.isEmpty(text)) {
text = getHint();
}
if (!TextUtils.isEmpty(text)) {
- if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
- text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
- }
event.getText().add(text);
}
- } else {
- event.setPassword(isPassword);
}
- return false;
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+
+ final boolean isPassword = hasPasswordTransformationMethod();
+ event.setPassword(isPassword);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ final boolean isPassword = hasPasswordTransformationMethod();
+ if (!isPassword) {
+ info.setText(getText());
+ }
+ info.setPassword(isPassword);
}
void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
@@ -8010,6 +8007,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* this will be {@link android.R.id#copyUrl}, {@link android.R.id#selectTextMode},
* {@link android.R.id#selectAll}, {@link android.R.id#paste}, {@link android.R.id#cut}
* or {@link android.R.id#copy}.
+ *
+ * @return true if the context menu item action was performed.
*/
public boolean onTextContextMenuItem(int id) {
int min = 0;
@@ -8046,7 +8045,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case ID_SELECTION_MODE:
if (mSelectionActionMode != null) {
// Selection mode is already started, simply change selected part.
- updateSelectedRegion();
+ selectCurrentWord();
} else {
startSelectionActionMode();
}
@@ -8054,7 +8053,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case ID_SELECT_ALL:
// This does not enter text selection mode. Text is highlighted, so that it can be
- // bulk edited, like selectAllOnFocus does.
+ // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
selectAll();
return true;
@@ -8178,10 +8177,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Long press in empty space moves cursor and shows the Paste affordance if available.
if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
mInsertionControllerEnabled) {
- final int offset = getOffset(mLastDownPositionX, mLastDownPositionY);
+ final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
stopSelectionActionMode();
- Selection.setSelection((Spannable)mText, offset);
- getInsertionController().show(0);
+ Selection.setSelection((Spannable) mText, offset);
+ getInsertionController().showWithPaste();
handled = true;
}
@@ -8196,8 +8195,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
- // New selection at touch position
- updateSelectedRegion();
+ selectCurrentWord();
}
handled = true;
}
@@ -8213,17 +8211,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return handled;
}
- /**
- * When selection mode is already started, this method simply updates the selected part of text
- * to the text under the finger.
- */
- private void updateSelectedRegion() {
- // Start a new selection at current position, keep selectionAction mode on
- selectCurrentWord();
- // Updates handles' positions
- getSelectionController().show();
- }
-
private boolean touchPositionIsInSelection() {
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
@@ -8246,6 +8233,460 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
}
+ private static class SuggestionRangeSpan extends UnderlineSpan {
+ // TODO themable, would be nice to make it a child class of TextAppearanceSpan, but
+ // there is no way to have underline and TextAppearanceSpan.
+ }
+
+ private class SuggestionsPopupWindow implements OnClickListener {
+ private static final int MAX_NUMBER_SUGGESTIONS = 5;
+ private static final int NO_SUGGESTIONS = -1;
+ private final PopupWindow mContainer;
+ private final ViewGroup[] mSuggestionViews = new ViewGroup[2];
+ private final int[] mSuggestionViewLayouts = new int[] {
+ mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout};
+ private WordIterator mSuggestionWordIterator;
+ private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan[0];
+
+ public SuggestionsPopupWindow() {
+ mContainer = new PopupWindow(TextView.this.mContext, null,
+ com.android.internal.R.attr.textSuggestionsWindowStyle);
+ mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+ mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+
+ mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+ mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ private class SuggestionInfo {
+ int suggestionStart, suggestionEnd; // range of suggestion item with replacement text
+ int spanStart, spanEnd; // range in TextView where text should be inserted
+ SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
+ int suggestionIndex; // the index of the suggestion inside suggestionSpan
+ }
+
+ private ViewGroup getViewGroup(boolean under) {
+ final int viewIndex = under ? 0 : 1;
+ ViewGroup viewGroup = mSuggestionViews[viewIndex];
+
+ if (viewGroup == null) {
+ final int layout = mSuggestionViewLayouts[viewIndex];
+ LayoutInflater inflater = (LayoutInflater) TextView.this.mContext.
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ if (inflater == null) {
+ throw new IllegalArgumentException(
+ "Unable to create TextEdit suggestion window inflater");
+ }
+
+ View view = inflater.inflate(layout, null);
+
+ if (! (view instanceof ViewGroup)) {
+ throw new IllegalArgumentException(
+ "Inflated TextEdit suggestion window is not a ViewGroup: " + view);
+ }
+
+ viewGroup = (ViewGroup) view;
+
+ // Inflate the suggestion items once and for all.
+ for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
+ View childView = inflater.inflate(mTextEditSuggestionItemLayout, viewGroup,
+ false);
+
+ if (! (childView instanceof TextView)) {
+ throw new IllegalArgumentException(
+ "Inflated TextEdit suggestion item is not a TextView: " + childView);
+ }
+
+ childView.setTag(new SuggestionInfo());
+ viewGroup.addView(childView);
+ childView.setOnClickListener(this);
+ }
+
+ mSuggestionViews[viewIndex] = viewGroup;
+ }
+
+ return viewGroup;
+ }
+
+ public void show() {
+ if (!(mText instanceof Editable)) return;
+
+ final int pos = TextView.this.getSelectionStart();
+ Spannable spannable = (Spannable)TextView.this.mText;
+ SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
+ final int nbSpans = suggestionSpans.length;
+
+ ViewGroup viewGroup = getViewGroup(true);
+ mContainer.setContentView(viewGroup);
+
+ int totalNbSuggestions = 0;
+ int spanUnionStart = mText.length();
+ int spanUnionEnd = 0;
+
+ for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
+ SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
+ final int spanStart = spannable.getSpanStart(suggestionSpan);
+ final int spanEnd = spannable.getSpanEnd(suggestionSpan);
+ spanUnionStart = Math.min(spanStart, spanUnionStart);
+ spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
+
+ String[] suggestions = suggestionSpan.getSuggestions();
+ int nbSuggestions = suggestions.length;
+ for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
+ TextView textView = (TextView) viewGroup.getChildAt(totalNbSuggestions);
+ textView.setText(suggestions[suggestionIndex]);
+ SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
+ suggestionInfo.spanStart = spanStart;
+ suggestionInfo.spanEnd = spanEnd;
+ suggestionInfo.suggestionSpan = suggestionSpan;
+ suggestionInfo.suggestionIndex = suggestionIndex;
+
+ totalNbSuggestions++;
+ if (totalNbSuggestions == MAX_NUMBER_SUGGESTIONS) {
+ // Also end outer for loop
+ spanIndex = nbSpans;
+ break;
+ }
+ }
+ }
+
+ if (totalNbSuggestions == 0) {
+ // TODO Replace by final text, use a dedicated layout, add a fade out timer...
+ TextView textView = (TextView) viewGroup.getChildAt(0);
+ textView.setText("No suggestions available");
+ SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
+ suggestionInfo.spanStart = NO_SUGGESTIONS;
+ totalNbSuggestions++;
+ } else {
+ if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
+ ((Editable) mText).setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ for (int i = 0; i < totalNbSuggestions; i++) {
+ final TextView textView = (TextView) viewGroup.getChildAt(i);
+ highlightTextDifferences(textView, spanUnionStart, spanUnionEnd);
+ }
+ }
+
+ for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
+ viewGroup.getChildAt(i).setVisibility(i < totalNbSuggestions ? VISIBLE : GONE);
+ }
+
+ final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ viewGroup.measure(size, size);
+
+ positionAtCursor();
+ }
+
+ private long[] getWordLimits(CharSequence text) {
+ // TODO locale for mSuggestionWordIterator
+ if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator();
+ mSuggestionWordIterator.setCharSequence(text);
+
+ // First pass will simply count the number of words to be able to create an array
+ // Not too expensive since previous break positions are cached by the BreakIterator
+ int nbWords = 0;
+ int position = mSuggestionWordIterator.following(0);
+ while (position != BreakIterator.DONE) {
+ nbWords++;
+ position = mSuggestionWordIterator.following(position);
+ }
+
+ int index = 0;
+ long[] result = new long[nbWords];
+
+ position = mSuggestionWordIterator.following(0);
+ while (position != BreakIterator.DONE) {
+ int wordStart = mSuggestionWordIterator.getBeginning(position);
+ result[index++] = packRangeInLong(wordStart, position);
+ position = mSuggestionWordIterator.following(position);
+ }
+
+ return result;
+ }
+
+ private TextAppearanceSpan highlightSpan(int index) {
+ final int length = mHighlightSpans.length;
+ if (index < length) {
+ return mHighlightSpans[index];
+ }
+
+ // Assumes indexes are requested in sequence: simply append one more item
+ TextAppearanceSpan[] newArray = new TextAppearanceSpan[length + 1];
+ System.arraycopy(mHighlightSpans, 0, newArray, 0, length);
+ TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
+ android.R.style.TextAppearance_SuggestionHighlight);
+ newArray[length] = highlightSpan;
+ mHighlightSpans = newArray;
+ return highlightSpan;
+ }
+
+ private void highlightTextDifferences(TextView textView, int unionStart, int unionEnd) {
+ SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
+ final int spanStart = suggestionInfo.spanStart;
+ final int spanEnd = suggestionInfo.spanEnd;
+
+ // Remove all text formating by converting to Strings
+ final String text = textView.getText().toString();
+ final String sourceText = mText.subSequence(spanStart, spanEnd).toString();
+
+ long[] sourceWordLimits = getWordLimits(sourceText);
+ long[] wordLimits = getWordLimits(text);
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+ // span [spanStart, spanEnd] is included in union [spanUnionStart, int spanUnionEnd]
+ // The final result is made of 3 parts: the text before, between and after the span
+ // This is the text before, provided for context
+ ssb.append(mText.subSequence(unionStart, spanStart).toString());
+
+ // shift is used to offset spans positions wrt span's beginning
+ final int shift = spanStart - unionStart;
+ suggestionInfo.suggestionStart = shift;
+ suggestionInfo.suggestionEnd = shift + text.length();
+
+ // This is the actual suggestion text, which will be highlighted by the following code
+ ssb.append(text);
+
+ String[] words = new String[wordLimits.length];
+ for (int i = 0; i < wordLimits.length; i++) {
+ int wordStart = extractRangeStartFromLong(wordLimits[i]);
+ int wordEnd = extractRangeEndFromLong(wordLimits[i]);
+ words[i] = text.substring(wordStart, wordEnd);
+ }
+
+ // Highlighted word algorithm is based on word matching between source and text
+ // Matching words are found from left to right. TODO: change for RTL languages
+ // Characters between matching words are highlighted
+ int previousCommonWordIndex = -1;
+ int nbHighlightSpans = 0;
+ for (int i = 0; i < sourceWordLimits.length; i++) {
+ int wordStart = extractRangeStartFromLong(sourceWordLimits[i]);
+ int wordEnd = extractRangeEndFromLong(sourceWordLimits[i]);
+ String sourceWord = sourceText.substring(wordStart, wordEnd);
+
+ for (int j = previousCommonWordIndex + 1; j < words.length; j++) {
+ if (sourceWord.equals(words[j])) {
+ if (j != previousCommonWordIndex + 1) {
+ int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
+ extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
+ int lastDifferentPosition = extractRangeStartFromLong(wordLimits[j]);
+ ssb.setSpan(highlightSpan(nbHighlightSpans++),
+ shift + firstDifferentPosition, shift + lastDifferentPosition,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ // Compare characters between words
+ int previousSourceWordEnd = i == 0 ? 0 :
+ extractRangeEndFromLong(sourceWordLimits[i - 1]);
+ int sourceWordStart = extractRangeStartFromLong(sourceWordLimits[i]);
+ String sourceSpaces = sourceText.substring(previousSourceWordEnd,
+ sourceWordStart);
+
+ int previousWordEnd = j == 0 ? 0 :
+ extractRangeEndFromLong(wordLimits[j - 1]);
+ int currentWordStart = extractRangeStartFromLong(wordLimits[j]);
+ String textSpaces = text.substring(previousWordEnd, currentWordStart);
+
+ if (!sourceSpaces.equals(textSpaces)) {
+ ssb.setSpan(highlightSpan(nbHighlightSpans++),
+ shift + previousWordEnd, shift + currentWordStart,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ previousCommonWordIndex = j;
+ break;
+ }
+ }
+ }
+
+ // Finally, compare ends of Strings
+ if (previousCommonWordIndex < words.length - 1) {
+ int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
+ extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
+ int lastDifferentPosition = textView.length();
+ ssb.setSpan(highlightSpan(nbHighlightSpans++),
+ shift + firstDifferentPosition, shift + lastDifferentPosition,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ int lastSourceWordEnd = sourceWordLimits.length == 0 ? 0 :
+ extractRangeEndFromLong(sourceWordLimits[sourceWordLimits.length - 1]);
+ String sourceSpaces = sourceText.substring(lastSourceWordEnd, sourceText.length());
+
+ int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 :
+ extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
+ String textSpaces = text.substring(lastCommonTextWordEnd, textView.length());
+
+ if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) {
+ ssb.setSpan(highlightSpan(nbHighlightSpans++),
+ shift + lastCommonTextWordEnd, shift + textView.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+
+ // Final part, text after the current suggestion range.
+ ssb.append(mText.subSequence(spanEnd, unionEnd).toString());
+ textView.setText(ssb);
+ }
+
+ public void hide() {
+ if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
+ ((Editable) mText).removeSpan(mSuggestionRangeSpan);
+ }
+ mContainer.dismiss();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view instanceof TextView) {
+ TextView textView = (TextView) view;
+ SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
+ final int spanStart = suggestionInfo.spanStart;
+ final int spanEnd = suggestionInfo.spanEnd;
+ if (spanStart != NO_SUGGESTIONS) {
+ // SuggestionSpans are removed by replace: save them before
+ Editable editable = ((Editable) mText);
+ SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
+ SuggestionSpan.class);
+ final int length = suggestionSpans.length;
+ int[] suggestionSpansStarts = new int[length];
+ int[] suggestionSpansEnds = new int[length];
+ int[] suggestionSpansFlags = new int[length];
+ for (int i = 0; i < length; i++) {
+ final SuggestionSpan suggestionSpan = suggestionSpans[i];
+ suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
+ suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
+ suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
+ }
+
+ final int suggestionStart = suggestionInfo.suggestionStart;
+ final int suggestionEnd = suggestionInfo.suggestionEnd;
+ final String suggestion = textView.getText().subSequence(
+ suggestionStart, suggestionEnd).toString();
+ final String originalText = mText.subSequence(spanStart, spanEnd).toString();
+ ((Editable) mText).replace(spanStart, spanEnd, suggestion);
+
+ // Swap text content between actual text and Suggestion span
+ String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
+ suggestions[suggestionInfo.suggestionIndex] = originalText;
+
+ // Notify source IME of the suggestion pick
+ if (!TextUtils.isEmpty(
+ suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
+ suggestionInfo.suggestionIndex);
+ }
+
+ // Restore previous SuggestionSpans
+ final int lengthDifference = suggestion.length() - (spanEnd - spanStart);
+ for (int i = 0; i < length; i++) {
+ // Only spans that include the modified region make sense after replacement
+ // Spans partially included in the replaced region are removed, there is no
+ // way to assign them a valid range after replacement
+ if (suggestionSpansStarts[i] <= spanStart &&
+ suggestionSpansEnds[i] >= spanEnd) {
+ editable.setSpan(suggestionSpans[i], suggestionSpansStarts[i],
+ suggestionSpansEnds[i] + lengthDifference,
+ suggestionSpansFlags[i]);
+ }
+ }
+ }
+ }
+ hide();
+ }
+
+ void positionAtCursor() {
+ View contentView = mContainer.getContentView();
+ int width = contentView.getMeasuredWidth();
+ int height = contentView.getMeasuredHeight();
+ final int offset = TextView.this.getSelectionStart();
+ final int line = mLayout.getLineForOffset(offset);
+ final int lineBottom = mLayout.getLineBottom(line);
+ float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
+
+ final Rect bounds = sCursorControllerTempRect;
+ bounds.left = (int) (primaryHorizontal - width / 2.0f);
+ bounds.top = lineBottom;
+
+ bounds.right = bounds.left + width;
+ bounds.bottom = bounds.top + height;
+
+ convertFromViewportToContentCoordinates(bounds);
+
+ final int[] coords = mTempCoords;
+ TextView.this.getLocationInWindow(coords);
+ coords[0] += bounds.left;
+ coords[1] += bounds.top;
+
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+ final int screenHeight = displayMetrics.heightPixels;
+
+ // Vertical clipping
+ if (coords[1] + height > screenHeight) {
+ // Try to position above current line instead
+ // TODO use top layout instead, reverse suggestion order,
+ // try full screen vertical down if it still does not fit. TBD with designers.
+
+ // Update dimensions from new view
+ contentView = mContainer.getContentView();
+ width = contentView.getMeasuredWidth();
+ height = contentView.getMeasuredHeight();
+
+ final int lineTop = mLayout.getLineTop(line);
+ final int lineHeight = lineBottom - lineTop;
+ coords[1] -= height + lineHeight;
+ }
+
+ // Horizontal clipping
+ coords[0] = Math.max(0, coords[0]);
+ coords[0] = Math.min(displayMetrics.widthPixels - width, coords[0]);
+
+ mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
+ }
+ }
+
+ void showSuggestions() {
+ if (!mSuggestionsEnabled || !isTextEditable()) return;
+
+ if (mSuggestionsPopupWindow == null) {
+ mSuggestionsPopupWindow = new SuggestionsPopupWindow();
+ }
+ hideControllers();
+ mSuggestionsPopupWindow.show();
+ }
+
+ void hideSuggestions() {
+ if (mSuggestionsPopupWindow != null) {
+ mSuggestionsPopupWindow.hide();
+ }
+ }
+
+ /**
+ * Some parts of the text can have alternate suggestion text attached. This is typically done by
+ * the IME by adding {@link SuggestionSpan}s to the text.
+ *
+ * When suggestions are enabled (default), this list of suggestions will be displayed when the
+ * user double taps on these parts of the text. No suggestions are displayed when this value is
+ * false. Use {@link #setSuggestionsEnabled(boolean)} to change this value.
+ *
+ * @return true if the suggestions popup window is enabled.
+ *
+ * @attr ref android.R.styleable#TextView_suggestionsEnabled
+ */
+ public boolean isSuggestionsEnabled() {
+ return mSuggestionsEnabled;
+ }
+
+ /**
+ * Enables or disables the suggestion popup. See {@link #isSuggestionsEnabled()}.
+ *
+ * @param enabled Whether or not suggestions are enabled.
+ */
+ public void setSuggestionsEnabled(boolean enabled) {
+ mSuggestionsEnabled = enabled;
+ }
+
/**
* If provided, this ActionMode.Callback will be used to create the ActionMode when text
* selection is initiated in this View.
@@ -8298,8 +8739,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (!hasSelection()) {
- // If selection mode is started after a device rotation, there is already a selection.
- selectCurrentWord();
+ // There may already be a selection on device rotation
+ boolean currentWordSelected = selectCurrentWord();
+ if (!currentWordSelected) {
+ // No word found under cursor or text selection not permitted.
+ return false;
+ }
}
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
@@ -8330,16 +8775,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
(ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
if (clip != null) {
- boolean didfirst = false;
+ boolean didFirst = false;
for (int i=0; i<clip.getItemCount(); i++) {
CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
if (paste != null) {
- if (!didfirst) {
+ if (!didFirst) {
long minMax = prepareSpacesAroundPaste(min, max, paste);
min = extractRangeStartFromLong(minMax);
max = extractRangeEndFromLong(minMax);
Selection.setSelection((Spannable) mText, max);
((Editable) mText).replace(min, max, paste);
+ didFirst = true;
} else {
((Editable) mText).insert(getSelectionEnd(), "\n");
((Editable) mText).insert(getSelectionEnd(), paste);
@@ -8370,10 +8816,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
- mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
+ boolean allowText = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.allow_action_menu_item_text_with_icon);
+
+ mode.setTitle(allowText ?
+ mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
mode.setSubtitle(null);
+ int selectAllIconId = 0; // No icon by default
+ if (!allowText) {
+ // Provide an icon, text will not be displayed on smaller screens.
+ selectAllIconId = styledAttributes.getResourceId(
+ R.styleable.Theme_actionModeSelectAllDrawable, 0);
+ }
+
menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+ setIcon(selectAllIconId).
setAlphabeticShortcut('a').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
@@ -8454,65 +8912,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- /**
- * A CursorController instance can be used to control a cursor in the text.
- * It is not used outside of {@link TextView}.
- * @hide
- */
- private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
- /**
- * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
- * See also {@link #hide()}.
- */
- public void show();
-
- /**
- * Hide the cursor controller from screen.
- * See also {@link #show()}.
- */
- public void hide();
-
- /**
- * @return true if the CursorController is currently visible
- */
- public boolean isShowing();
-
- /**
- * Update the controller's position.
- */
- public void updatePosition(HandleView handle, int x, int y);
-
- public void updateOffset(HandleView handle, int offset);
-
- public void updatePosition();
-
- public int getCurrentOffset(HandleView handle);
-
- /**
- * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
- * a chance to become active and/or visible.
- * @param event The touch event
- */
- public boolean onTouchEvent(MotionEvent event);
-
- /**
- * Called when the view is detached from window. Perform house keeping task, such as
- * stopping Runnable thread that would otherwise keep a reference on the context, thus
- * preventing the activity to be recycled.
- */
- public void onDetached();
- }
-
- private class PastePopupMenu implements OnClickListener {
+ private class PastePopupWindow implements OnClickListener {
private final PopupWindow mContainer;
- private int mPositionX;
- private int mPositionY;
private final View[] mPasteViews = new View[4];
private final int[] mPasteViewLayouts = new int[] {
mTextEditPasteWindowLayout, mTextEditNoPasteWindowLayout,
mTextEditSidePasteWindowLayout, mTextEditSideNoPasteWindowLayout };
- public PastePopupMenu() {
+ public PastePopupWindow() {
mContainer = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
@@ -8595,14 +9002,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
convertFromViewportToContentCoordinates(bounds);
- mPositionX = bounds.left;
- mPositionY = bounds.top;
-
-
final int[] coords = mTempCoords;
TextView.this.getLocationInWindow(coords);
- coords[0] += mPositionX;
- coords[1] += mPositionY;
+ coords[0] += bounds.left;
+ coords[1] += bounds.top;
final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
if (coords[1] < 0) {
@@ -8638,30 +9041,54 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private class HandleView extends View implements ViewTreeObserver.OnPreDrawListener {
- private Drawable mDrawable;
+ private abstract class HandleView extends View implements ViewTreeObserver.OnPreDrawListener {
+ protected Drawable mDrawable;
private final PopupWindow mContainer;
// Position with respect to the parent TextView
private int mPositionX, mPositionY;
- private final CursorController mController;
private boolean mIsDragging;
// Offset from touch position to mPosition
private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
- private float mHotspotX;
+ protected float mHotspotX;
// Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
private float mTouchOffsetY;
// Where the touch position should be on the handle to ensure a maximum cursor visibility
private float mIdealVerticalOffset;
- // Parent's (TextView) position in window
+ // Parent's (TextView) previous position in window
private int mLastParentX, mLastParentY;
- private float mDownPositionX, mDownPositionY;
// PopupWindow container absolute position with respect to the enclosing window
private int mContainerPositionX, mContainerPositionY;
// Visible or not (scrolled off screen), whether or not this handle should be visible
private boolean mIsActive = false;
- // The insertion handle can have an associated PastePopupMenu
- private boolean mIsInsertionHandle = false;
- private PastePopupMenu mPastePopupWindow;
+ // Used to detect that setFrame was called
+ private boolean mNeedsUpdate = true;
+
+ public HandleView() {
+ super(TextView.this.mContext);
+ mContainer = new PopupWindow(TextView.this.mContext, null,
+ com.android.internal.R.attr.textSelectHandleWindowStyle);
+ mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+ mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+ mContainer.setContentView(this);
+
+ initDrawable();
+
+ final int handleHeight = mDrawable.getIntrinsicHeight();
+ mTouchOffsetY = -0.3f * handleHeight;
+ mIdealVerticalOffset = 0.7f * handleHeight;
+ }
+
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean changed = super.setFrame(left, top, right, bottom);
+ // onPreDraw is called for PhoneWindow before the layout of this view is
+ // performed. Make sure to update position, even if container didn't move.
+ if (changed) mNeedsUpdate = true;
+ return changed;
+ }
+
+ protected abstract void initDrawable();
// Touch-up filter: number of previous positions remembered
private static final int HISTORY_SIZE = 5;
@@ -8702,71 +9129,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (i > 0 && i < iMax &&
(now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
- mController.updateOffset(this, mPreviousOffsets[index]);
- }
- }
-
- public static final int LEFT = 0;
- public static final int CENTER = 1;
- public static final int RIGHT = 2;
-
- public HandleView(CursorController controller, int pos) {
- super(TextView.this.mContext);
- mController = controller;
- mContainer = new PopupWindow(TextView.this.mContext, null,
- com.android.internal.R.attr.textSelectHandleWindowStyle);
- mContainer.setSplitTouchEnabled(true);
- mContainer.setClippingEnabled(false);
- mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
- mContainer.setContentView(this);
-
- setPosition(pos);
- }
-
- private void setPosition(int pos) {
- int handleWidth;
- switch (pos) {
- case LEFT: {
- if (mSelectHandleLeft == null) {
- mSelectHandleLeft = mContext.getResources().getDrawable(
- mTextSelectHandleLeftRes);
- }
- mDrawable = mSelectHandleLeft;
- handleWidth = mDrawable.getIntrinsicWidth();
- mHotspotX = handleWidth * 3.0f / 4.0f;
- break;
- }
-
- case RIGHT: {
- if (mSelectHandleRight == null) {
- mSelectHandleRight = mContext.getResources().getDrawable(
- mTextSelectHandleRightRes);
- }
- mDrawable = mSelectHandleRight;
- handleWidth = mDrawable.getIntrinsicWidth();
- mHotspotX = handleWidth / 4.0f;
- break;
- }
-
- case CENTER:
- default: {
- if (mSelectHandleCenter == null) {
- mSelectHandleCenter = mContext.getResources().getDrawable(
- mTextSelectHandleRes);
- }
- mDrawable = mSelectHandleCenter;
- handleWidth = mDrawable.getIntrinsicWidth();
- mHotspotX = handleWidth / 2.0f;
- mIsInsertionHandle = true;
- break;
- }
+ updateOffset(mPreviousOffsets[index]);
}
-
- final int handleHeight = mDrawable.getIntrinsicHeight();
- mTouchOffsetY = -0.3f * handleHeight;
- mIdealVerticalOffset = 0.7f * handleHeight;
-
- invalidate();
}
@Override
@@ -8775,12 +9139,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public void show() {
- updateContainerPosition();
if (isShowing()) {
mContainer.update(mContainerPositionX, mContainerPositionY,
mRight - mLeft, mBottom - mTop);
-
- hidePastePopupWindow();
} else {
mContainer.showAtLocation(TextView.this, 0,
mContainerPositionX, mContainerPositionY);
@@ -8792,10 +9153,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void dismiss() {
+ protected void dismiss() {
mIsDragging = false;
mContainer.dismiss();
- hidePastePopupWindow();
}
public void hide() {
@@ -8826,24 +9186,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
- final TextView hostView = TextView.this;
+ final TextView textView = TextView.this;
- if (mTempRect == null) {
- mTempRect = new Rect();
- }
+ if (mTempRect == null) mTempRect = new Rect();
final Rect clip = mTempRect;
clip.left = compoundPaddingLeft;
clip.top = extendedPaddingTop;
- clip.right = hostView.getWidth() - compoundPaddingRight;
- clip.bottom = hostView.getHeight() - extendedPaddingBottom;
+ clip.right = textView.getWidth() - compoundPaddingRight;
+ clip.bottom = textView.getHeight() - extendedPaddingBottom;
- final ViewParent parent = hostView.getParent();
- if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
+ final ViewParent parent = textView.getParent();
+ if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
return false;
}
final int[] coords = mTempCoords;
- hostView.getLocationInWindow(coords);
+ textView.getLocationInWindow(coords);
final int posX = coords[0] + mPositionX + (int) mHotspotX;
final int posY = coords[1] + mPositionY;
@@ -8852,45 +9210,60 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
posY >= clip.top && posY <= clip.bottom;
}
- private void moveTo(int x, int y) {
- mPositionX = x - TextView.this.mScrollX;
- mPositionY = y - TextView.this.mScrollY;
+ public abstract int getCurrentCursorOffset();
- if (mIsDragging) {
- TextView.this.getLocationInWindow(mTempCoords);
- if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
- mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
- mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
- mLastParentX = mTempCoords[0];
- mLastParentY = mTempCoords[1];
- }
- // Hide paste popup window as soon as the handle is dragged.
- hidePastePopupWindow();
+ public abstract void updateOffset(int offset);
+
+ public abstract void updatePosition(float x, float y);
+
+ protected void positionAtCursorOffset(int offset) {
+ // A HandleView relies on the layout, which may be nulled by external methods.
+ if (mLayout == null) {
+ // Will update controllers' state, hiding them and stopping selection mode if needed
+ prepareCursorControllers();
+ return;
}
+
+ addPositionToTouchUpFilter(offset);
+ final int line = mLayout.getLineForOffset(offset);
+ final int lineBottom = mLayout.getLineBottom(line);
+
+ mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
+ mPositionY = lineBottom;
+
+ // Take TextView's padding into account.
+ mPositionX += viewportToContentHorizontalOffset();
+ mPositionY += viewportToContentVerticalOffset();
}
- /**
- * Updates the global container's position.
- * @return whether or not the position has actually changed
- */
- private boolean updateContainerPosition() {
- // TODO Prevent this using different HandleView subclasses
- mController.updateOffset(this, mController.getCurrentOffset(this));
+ private void checkForContainerPositionChange() {
+ positionAtCursorOffset(getCurrentCursorOffset());
+
+ final int previousContainerPositionX = mContainerPositionX;
+ final int previousContainerPositionY = mContainerPositionY;
+
TextView.this.getLocationInWindow(mTempCoords);
- final int containerPositionX = mTempCoords[0] + mPositionX;
- final int containerPositionY = mTempCoords[1] + mPositionY;
+ mContainerPositionX = mTempCoords[0] + mPositionX;
+ mContainerPositionY = mTempCoords[1] + mPositionY;
- if (containerPositionX != mContainerPositionX ||
- containerPositionY != mContainerPositionY) {
- mContainerPositionX = containerPositionX;
- mContainerPositionY = containerPositionY;
- return true;
- }
- return false;
+ mNeedsUpdate |= previousContainerPositionX != mContainerPositionX;
+ mNeedsUpdate |= previousContainerPositionY != mContainerPositionY;
}
public boolean onPreDraw() {
- if (updateContainerPosition()) {
+ checkForContainerPositionChange();
+ if (mNeedsUpdate) {
+ if (mIsDragging) {
+ if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
+ mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
+ mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
+ mLastParentX = mTempCoords[0];
+ mLastParentY = mTempCoords[1];
+ }
+ }
+
+ onHandleMoved();
+
if (isPositionVisible()) {
mContainer.update(mContainerPositionX, mContainerPositionY,
mRight - mLeft, mBottom - mTop);
@@ -8903,9 +9276,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
dismiss();
}
}
-
- // Hide paste popup as soon as the view is scrolled or moved
- hidePastePopupWindow();
+ mNeedsUpdate = false;
}
return true;
}
@@ -8920,11 +9291,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- startTouchUpFilter(mController.getCurrentOffset(this));
- mDownPositionX = ev.getRawX();
- mDownPositionY = ev.getRawY();
- mTouchToWindowOffsetX = mDownPositionX - mPositionX;
- mTouchToWindowOffsetY = mDownPositionY - mPositionY;
+ startTouchUpFilter(getCurrentCursorOffset());
+ mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
+ mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
final int[] coords = mTempCoords;
TextView.this.getLocationInWindow(coords);
@@ -8954,24 +9323,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
- mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
+ updatePosition(newPosX, newPosY);
break;
}
case MotionEvent.ACTION_UP:
- if (mIsInsertionHandle) {
- final float deltaX = mDownPositionX - ev.getRawX();
- final float deltaY = mDownPositionY - ev.getRawY();
- final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
- if (distanceSquared < mSquaredTouchSlopDistance) {
- if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) {
- // Tapping on the handle dismisses the displayed paste view,
- mPastePopupWindow.hide();
- } else {
- ((InsertionPointCursorController) mController).show(0);
- }
- }
- }
filterOnTouchUp();
mIsDragging = false;
break;
@@ -8987,60 +9343,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mIsDragging;
}
- void positionAtCursor(int offset) {
- addPositionToTouchUpFilter(offset);
- final int width = mDrawable.getIntrinsicWidth();
- final int height = mDrawable.getIntrinsicHeight();
- final int line = mLayout.getLineForOffset(offset);
- final int lineBottom = mLayout.getLineBottom(line);
-
- final Rect bounds = sCursorControllerTempRect;
- bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX) +
- TextView.this.mScrollX;
- bounds.top = lineBottom + TextView.this.mScrollY;
-
- bounds.right = bounds.left + width;
- bounds.bottom = bounds.top + height;
-
- convertFromViewportToContentCoordinates(bounds);
- moveTo(bounds.left, bounds.top);
- }
-
- void showPastePopupWindow() {
- if (mIsInsertionHandle) {
- if (mPastePopupWindow == null) {
- // Lazy initialisation: create when actually shown only.
- mPastePopupWindow = new PastePopupMenu();
- }
- mPastePopupWindow.show();
- }
+ void onHandleMoved() {
+ // Does nothing by default
}
- void hidePastePopupWindow() {
- if (mPastePopupWindow != null) {
- mPastePopupWindow.hide();
- }
+ public void onDetached() {
+ // Should be overriden to clean possible Runnable
}
}
- private class InsertionPointCursorController implements CursorController {
+ private class InsertionHandleView extends HandleView {
private static final int DELAY_BEFORE_FADE_OUT = 4000;
- private static final int DELAY_BEFORE_PASTE = 2000;
- private static final int RECENT_CUT_COPY_DURATION = 15 * 1000;
+ private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
- // The cursor controller image. Lazily created.
- private HandleView mHandle;
+ // Used to detect taps on the insertion handle, which will affect the PastePopupWindow
+ private float mDownPositionX, mDownPositionY;
+ private PastePopupWindow mPastePopupWindow;
private Runnable mHider;
private Runnable mPastePopupShower;
+ @Override
public void show() {
- show(DELAY_BEFORE_PASTE);
+ super.show();
+ hideDelayed();
+ hidePastePopupWindow();
}
public void show(int delayBeforePaste) {
- getHandle().show();
- hideDelayed();
- removePastePopupCallback();
+ show();
+
final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
delayBeforePaste = 0;
@@ -9049,81 +9380,252 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mPastePopupShower == null) {
mPastePopupShower = new Runnable() {
public void run() {
- getHandle().showPastePopupWindow();
+ showPastePopupWindow();
}
};
}
- postDelayed(mPastePopupShower, delayBeforePaste);
+ TextView.this.postDelayed(mPastePopupShower, delayBeforePaste);
}
}
- private void removePastePopupCallback() {
- if (mPastePopupShower != null) {
- removeCallbacks(mPastePopupShower);
+ @Override
+ protected void dismiss() {
+ super.dismiss();
+ onDetached();
+ }
+
+ private void hideDelayed() {
+ removeHiderCallback();
+ if (mHider == null) {
+ mHider = new Runnable() {
+ public void run() {
+ hide();
+ }
+ };
}
+ TextView.this.postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
}
private void removeHiderCallback() {
if (mHider != null) {
- removeCallbacks(mHider);
+ TextView.this.removeCallbacks(mHider);
}
}
- public void hide() {
- if (mHandle != null) {
- mHandle.hide();
+ @Override
+ protected void initDrawable() {
+ if (mSelectHandleCenter == null) {
+ mSelectHandleCenter = mContext.getResources().getDrawable(
+ mTextSelectHandleRes);
}
- removeHiderCallback();
- removePastePopupCallback();
+ mDrawable = mSelectHandleCenter;
+ mHotspotX = mDrawable.getIntrinsicWidth() / 2.0f;
}
- private void hideDelayed() {
- removeHiderCallback();
- if (mHider == null) {
- mHider = new Runnable() {
- public void run() {
- hide();
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final boolean result = super.onTouchEvent(ev);
+
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownPositionX = ev.getRawX();
+ mDownPositionY = ev.getRawY();
+ break;
+
+ case MotionEvent.ACTION_UP:
+ final float deltaX = mDownPositionX - ev.getRawX();
+ final float deltaY = mDownPositionY - ev.getRawY();
+ final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
+ if (distanceSquared < mSquaredTouchSlopDistance) {
+ if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) {
+ // Tapping on the handle dismisses the displayed paste view,
+ mPastePopupWindow.hide();
+ } else {
+ show(0);
+ }
}
- };
+ hideDelayed();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ hideDelayed();
+ break;
+
+ default:
+ break;
}
- postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
+
+ return result;
}
- public boolean isShowing() {
- return mHandle != null && mHandle.isShowing();
+ @Override
+ public int getCurrentCursorOffset() {
+ return TextView.this.getSelectionStart();
+ }
+
+ @Override
+ public void updateOffset(int offset) {
+ Selection.setSelection((Spannable) mText, offset);
}
- public void updatePosition(HandleView handle, int x, int y) {
- final int previousOffset = getSelectionStart();
- final int newOffset = getOffset(x, y);
+ @Override
+ public void updatePosition(float x, float y) {
+ updateOffset(getOffsetForPosition(x, y));
+ }
- if (newOffset != previousOffset) {
- updateOffset(handle, newOffset);
- removePastePopupCallback();
+ void showPastePopupWindow() {
+ if (mPastePopupWindow == null) {
+ mPastePopupWindow = new PastePopupWindow();
}
- hideDelayed();
+ mPastePopupWindow.show();
}
- public void updateOffset(HandleView handle, int offset) {
- Selection.setSelection((Spannable) mText, offset);
- updatePosition();
+ @Override
+ void onHandleMoved() {
+ removeHiderCallback();
+ hidePastePopupWindow();
+ }
+
+ void hidePastePopupWindow() {
+ if (mPastePopupShower != null) {
+ TextView.this.removeCallbacks(mPastePopupShower);
+ }
+ if (mPastePopupWindow != null) {
+ mPastePopupWindow.hide();
+ }
}
- public void updatePosition() {
- final int offset = getSelectionStart();
+ @Override
+ public void onDetached() {
+ removeHiderCallback();
+ hidePastePopupWindow();
+ }
+ }
- if (offset < 0) {
- // Should never happen, safety check.
- Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
- hide();
- return;
+ private class SelectionStartHandleView extends HandleView {
+ @Override
+ protected void initDrawable() {
+ if (mSelectHandleLeft == null) {
+ mSelectHandleLeft = mContext.getResources().getDrawable(
+ mTextSelectHandleLeftRes);
}
+ mDrawable = mSelectHandleLeft;
+ mHotspotX = mDrawable.getIntrinsicWidth() * 3.0f / 4.0f;
+ }
+
+ @Override
+ public int getCurrentCursorOffset() {
+ return TextView.this.getSelectionStart();
+ }
+
+ @Override
+ public void updateOffset(int offset) {
+ Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
+ }
+
+ @Override
+ public void updatePosition(float x, float y) {
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ int offset = getOffsetForPosition(x, y);
+
+ // No need to redraw when the offset is unchanged
+ if (offset == selectionStart) return;
+ // Handles can not cross and selection is at least one character
+ if (offset >= selectionEnd) offset = selectionEnd - 1;
+
+ Selection.setSelection((Spannable) mText, offset, selectionEnd);
+ }
+ }
+
+ private class SelectionEndHandleView extends HandleView {
+ @Override
+ protected void initDrawable() {
+ if (mSelectHandleRight == null) {
+ mSelectHandleRight = mContext.getResources().getDrawable(
+ mTextSelectHandleRightRes);
+ }
+ mDrawable = mSelectHandleRight;
+ mHotspotX = mDrawable.getIntrinsicWidth() / 4.0f;
+ }
+
+ @Override
+ public int getCurrentCursorOffset() {
+ return TextView.this.getSelectionEnd();
+ }
+
+ @Override
+ public void updateOffset(int offset) {
+ Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
+ }
+
+ @Override
+ public void updatePosition(float x, float y) {
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ int offset = getOffsetForPosition(x, y);
+
+ // No need to redraw when the offset is unchanged
+ if (offset == selectionEnd) return;
+ // Handles can not cross and selection is at least one character
+ if (offset <= selectionStart) offset = selectionStart + 1;
+
+ Selection.setSelection((Spannable) mText, selectionStart, offset);
+ }
+ }
+
+ /**
+ * A CursorController instance can be used to control a cursor in the text.
+ * It is not used outside of {@link TextView}.
+ * @hide
+ */
+ private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
+ /**
+ * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
+ * See also {@link #hide()}.
+ */
+ public void show();
- getHandle().positionAtCursor(offset);
+ /**
+ * Hide the cursor controller from screen.
+ * See also {@link #show()}.
+ */
+ public void hide();
+
+ /**
+ * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
+ * a chance to become active and/or visible.
+ * @param event The touch event
+ */
+ public boolean onTouchEvent(MotionEvent event);
+
+ /**
+ * Called when the view is detached from window. Perform house keeping task, such as
+ * stopping Runnable thread that would otherwise keep a reference on the context, thus
+ * preventing the activity from being recycled.
+ */
+ public void onDetached();
+ }
+
+ private class InsertionPointCursorController implements CursorController {
+ private static final int DELAY_BEFORE_PASTE = 2000;
+
+ private InsertionHandleView mHandle;
+
+ public void show() {
+ ((InsertionHandleView) getHandle()).show(DELAY_BEFORE_PASTE);
+ }
+
+ public void showWithPaste() {
+ ((InsertionHandleView) getHandle()).show(0);
}
- public int getCurrentOffset(HandleView handle) {
- return getSelectionStart();
+ public void hide() {
+ if (mHandle != null) {
+ mHandle.hide();
+ }
}
public boolean onTouchEvent(MotionEvent ev) {
@@ -9138,30 +9640,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private HandleView getHandle() {
if (mHandle == null) {
- mHandle = new HandleView(this, HandleView.CENTER);
+ mHandle = new InsertionHandleView();
}
return mHandle;
}
@Override
public void onDetached() {
- removeHiderCallback();
- removePastePopupCallback();
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnTouchModeChangeListener(this);
+
+ if (mHandle != null) mHandle.onDetached();
}
}
private class SelectionModifierCursorController implements CursorController {
- // The cursor controller images, lazily created when shown.
- private HandleView mStartHandle, mEndHandle;
+ // The cursor controller handles, lazily created when shown.
+ private SelectionStartHandleView mStartHandle;
+ private SelectionEndHandleView mEndHandle;
// The offsets of that last touch down event. Remembered to start selection there.
private int mMinTouchOffset, mMaxTouchOffset;
- // Whether selection anchors are active
- private boolean mIsShowing;
// Double tap detection
private long mPreviousTapUpTime = 0;
- private int mPreviousTapPositionX;
- private int mPreviousTapPositionY;
+ private float mPreviousTapPositionX, mPreviousTapPositionY;
SelectionModifierCursorController() {
resetTouchOffsets();
@@ -9173,96 +9675,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Lazy object creation has to be done before updatePosition() is called.
- if (mStartHandle == null) mStartHandle = new HandleView(this, HandleView.LEFT);
- if (mEndHandle == null) mEndHandle = new HandleView(this, HandleView.RIGHT);
-
- mIsShowing = true;
+ if (mStartHandle == null) mStartHandle = new SelectionStartHandleView();
+ if (mEndHandle == null) mEndHandle = new SelectionEndHandleView();
mStartHandle.show();
mEndHandle.show();
hideInsertionPointCursorController();
+ hideSuggestions();
}
public void hide() {
if (mStartHandle != null) mStartHandle.hide();
if (mEndHandle != null) mEndHandle.hide();
- mIsShowing = false;
- }
-
- public boolean isShowing() {
- return mIsShowing;
- }
-
- public void updatePosition(HandleView handle, int x, int y) {
- int selectionStart = getSelectionStart();
- int selectionEnd = getSelectionEnd();
-
- int offset = getOffset(x, y);
-
- // Handle the case where start and end are swapped, making sure start <= end
- if (handle == mStartHandle) {
- if (selectionStart == offset || offset > selectionEnd) {
- return; // no change, no need to redraw;
- }
- // If the user "closes" the selection entirely they were probably trying to
- // select a single character. Help them out.
- if (offset == selectionEnd) {
- offset = selectionEnd - 1;
- }
- selectionStart = offset;
- } else {
- if (selectionEnd == offset || offset < selectionStart) {
- return; // no change, no need to redraw;
- }
- // If the user "closes" the selection entirely they were probably trying to
- // select a single character. Help them out.
- if (offset == selectionStart) {
- offset = selectionStart + 1;
- }
- selectionEnd = offset;
- }
-
- Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
- updatePosition();
- }
-
- public void updateOffset(HandleView handle, int offset) {
- int start = getSelectionStart();
- int end = getSelectionEnd();
-
- if (mStartHandle == handle) {
- start = offset;
- } else {
- end = offset;
- }
-
- Selection.setSelection((Spannable) mText, start, end);
- updatePosition();
- }
-
- public void updatePosition() {
- if (!isShowing()) {
- return;
- }
-
- final int selectionStart = getSelectionStart();
- final int selectionEnd = getSelectionEnd();
-
- if ((selectionStart < 0) || (selectionEnd < 0)) {
- // Should never happen, safety check.
- Log.w(LOG_TAG, "Update selection controller position called with no cursor");
- hide();
- return;
- }
-
- // The handles have been created since the controller isShowing().
- mStartHandle.positionAtCursor(selectionStart);
- mEndHandle.positionAtCursor(selectionEnd);
- }
-
- public int getCurrentOffset(HandleView handle) {
- return mStartHandle == handle ? getSelectionStart() : getSelectionEnd();
}
public boolean onTouchEvent(MotionEvent event) {
@@ -9271,21 +9696,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (isTextEditable() || mTextIsSelectable) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
+ final float x = event.getX();
+ final float y = event.getY();
// Remember finger down position, to be able to start selection from there
- mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
+ mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
// Double tap detection
long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
if (duration <= ViewConfiguration.getDoubleTapTimeout() &&
isPositionOnText(x, y)) {
- final int deltaX = x - mPreviousTapPositionX;
- final int deltaY = y - mPreviousTapPositionY;
- final int distanceSquared = deltaX * deltaX + deltaY * deltaY;
+ final float deltaX = x - mPreviousTapPositionX;
+ final float deltaY = y - mPreviousTapPositionY;
+ final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
if (distanceSquared < mSquaredTouchSlopDistance) {
- startSelectionActionMode();
+ showSuggestions();
mDiscardNextActionUp = true;
}
}
@@ -9319,9 +9744,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void updateMinAndMaxOffsets(MotionEvent event) {
int pointerCount = event.getPointerCount();
for (int index = 0; index < pointerCount; index++) {
- final int x = (int) event.getX(index);
- final int y = (int) event.getY(index);
- int offset = getOffset(x, y);
+ int offset = getOffsetForPosition(event.getX(index), event.getY(index));
if (offset < mMinTouchOffset) mMinTouchOffset = offset;
if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
}
@@ -9354,7 +9777,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public void onDetached() {
- // Nothing to do
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnTouchModeChangeListener(this);
+
+ if (mStartHandle != null) mStartHandle.onDetached();
+ if (mEndHandle != null) mEndHandle.onDetached();
}
}
@@ -9371,44 +9798,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void hideControllers() {
hideInsertionPointCursorController();
stopSelectionActionMode();
+ hideSuggestions();
}
/**
- * Get the offset character closest to the specified absolute position.
+ * Get the character offset closest to the specified absolute position. A typical use case is to
+ * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
*
* @param x The horizontal absolute position of a point on screen
* @param y The vertical absolute position of a point on screen
* @return the character offset for the character whose position is closest to the specified
* position. Returns -1 if there is no layout.
- *
- * @hide
*/
- public int getOffset(int x, int y) {
+ public int getOffsetForPosition(float x, float y) {
if (getLayout() == null) return -1;
final int line = getLineAtCoordinate(y);
final int offset = getOffsetAtCoordinate(line, x);
return offset;
}
- private int convertToLocalHorizontalCoordinate(int x) {
+ private float convertToLocalHorizontalCoordinate(float x) {
x -= getTotalPaddingLeft();
// Clamp the position to inside of the view.
- x = Math.max(0, x);
+ x = Math.max(0.0f, x);
x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
x += getScrollX();
return x;
}
- private int getLineAtCoordinate(int y) {
+ private int getLineAtCoordinate(float y) {
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
- y = Math.max(0, y);
+ y = Math.max(0.0f, y);
y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
y += getScrollY();
- return getLayout().getLineForVertical(y);
+ return getLayout().getLineForVertical((int) y);
}
- private int getOffsetAtCoordinate(int line, int x) {
+ private int getOffsetAtCoordinate(int line, float x) {
x = convertToLocalHorizontalCoordinate(x);
return getLayout().getOffsetForHorizontal(line, x);
}
@@ -9416,7 +9843,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
* in the view. Returns false when the position is in the empty space of left/right of text.
*/
- private boolean isPositionOnText(int x, int y) {
+ private boolean isPositionOnText(float x, float y) {
if (getLayout() == null) return false;
final int line = getLineAtCoordinate(y);
@@ -9438,7 +9865,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
case DragEvent.ACTION_DRAG_LOCATION:
- final int offset = getOffset((int) event.getX(), (int) event.getY());
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
Selection.setSelection((Spannable)mText, offset);
return true;
@@ -9462,7 +9889,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
content.append(item.coerceToText(TextView.this.mContext));
}
- final int offset = getOffset((int) event.getX(), (int) event.getY());
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
Object localState = event.getLocalState();
DragLocalState dragLocalState = null;
@@ -9617,7 +10044,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean mSelectAllOnFocus = false;
- private int mGravity = Gravity.TOP | Gravity.LEFT;
+ private int mGravity = Gravity.TOP | Gravity.START;
private boolean mHorizontallyScrolling;
private int mAutoLinkMask;
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 029d690..423e735 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -409,7 +409,9 @@ public class TimePicker extends FrameLayout {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
int flags = DateUtils.FORMAT_SHOW_TIME;
if (mIs24HourView) {
flags |= DateUtils.FORMAT_24HOUR;
@@ -421,7 +423,6 @@ public class TimePicker extends FrameLayout {
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mTempCalendar.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
- return true;
}
private void updateHourControl() {
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index 450c966..9e37c7b 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -33,7 +33,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.WindowManager.LayoutParams;
@@ -501,7 +501,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
} else {
- ViewRoot viewRoot = getOwnerViewRoot();
+ ViewAncestor viewRoot = getOwnerViewAncestor();
if (viewRoot != null) {
viewRoot.dispatchKey(event);
}
@@ -526,15 +526,15 @@ public class ZoomButtonsController implements View.OnTouchListener {
}
}
- private ViewRoot getOwnerViewRoot() {
+ private ViewAncestor getOwnerViewAncestor() {
View rootViewOfOwner = mOwnerView.getRootView();
if (rootViewOfOwner == null) {
return null;
}
ViewParent parentOfRootView = rootViewOfOwner.getParent();
- if (parentOfRootView instanceof ViewRoot) {
- return (ViewRoot) parentOfRootView;
+ if (parentOfRootView instanceof ViewAncestor) {
+ return (ViewAncestor) parentOfRootView;
} else {
return null;
}
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 8f1354b..8d5df6f 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -16,24 +16,27 @@
package com.android.internal.app;
+import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.SubMenuBuilder;
import com.android.internal.widget.ActionBarContainer;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.ActionBarView;
+import com.android.internal.widget.ScrollingTabContainerView;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
-import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.ActionMode;
@@ -43,9 +46,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
import android.widget.SpinnerAdapter;
import java.lang.ref.WeakReference;
@@ -59,8 +59,7 @@ import java.util.ArrayList;
* which is normally hidden.
*/
public class ActionBarImpl extends ActionBar {
- private static final int NORMAL_VIEW = 0;
- private static final int CONTEXT_VIEW = 1;
+ private static final String TAG = "ActionBarImpl";
private Context mContext;
private Activity mActivity;
@@ -68,9 +67,10 @@ public class ActionBarImpl extends ActionBar {
private ActionBarContainer mContainerView;
private ActionBarView mActionView;
- private ActionBarContextView mUpperContextView;
- private LinearLayout mLowerContextView;
+ private ActionBarContextView mContextView;
+ private ActionBarContainer mSplitView;
private View mContentView;
+ private ScrollingTabContainerView mTabScrollView;
private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
@@ -89,63 +89,18 @@ public class ActionBarImpl extends ActionBar {
private static final int INVALID_POSITION = -1;
private int mContextDisplayMode;
+ private boolean mHasEmbeddedTabs;
+ private int mContentHeight;
final Handler mHandler = new Handler();
+ Runnable mTabSelector;
- private Animator mCurrentAnim;
+ private Animator mCurrentShowAnim;
+ private Animator mCurrentModeAnim;
private boolean mShowHideAnimationEnabled;
+ boolean mWasHiddenBeforeMode;
- private static final TimeInterpolator sFadeOutInterpolator = new DecelerateInterpolator();
-
- final AnimatorListener[] mAfterAnimation = new AnimatorListener[] {
- new AnimatorListener() { // NORMAL_VIEW
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mLowerContextView != null) {
- mLowerContextView.removeAllViews();
- }
- mCurrentAnim = null;
- hideAllExcept(NORMAL_VIEW);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- },
- new AnimatorListener() { // CONTEXT_VIEW
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mCurrentAnim = null;
- hideAllExcept(CONTEXT_VIEW);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- }
- };
-
- final AnimatorListener mHideListener = new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
+ final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mContentView != null) {
@@ -153,36 +108,16 @@ public class ActionBarImpl extends ActionBar {
}
mContainerView.setVisibility(View.GONE);
mContainerView.setTransitioning(false);
- mCurrentAnim = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
+ mCurrentShowAnim = null;
}
};
- final AnimatorListener mShowListener = new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
+ final AnimatorListener mShowListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mCurrentAnim = null;
+ mCurrentShowAnim = null;
mContainerView.requestLayout();
}
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
};
public ActionBarImpl(Activity activity) {
@@ -203,21 +138,69 @@ public class ActionBarImpl extends ActionBar {
private void init(View decor) {
mContext = decor.getContext();
mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
- mUpperContextView = (ActionBarContextView) decor.findViewById(
+ mContextView = (ActionBarContextView) decor.findViewById(
com.android.internal.R.id.action_context_bar);
- mLowerContextView = (LinearLayout) decor.findViewById(
- com.android.internal.R.id.lower_action_context_bar);
mContainerView = (ActionBarContainer) decor.findViewById(
com.android.internal.R.id.action_bar_container);
+ mSplitView = (ActionBarContainer) decor.findViewById(
+ com.android.internal.R.id.split_action_bar);
- if (mActionView == null || mUpperContextView == null || mContainerView == null) {
+ if (mActionView == null || mContextView == null || mContainerView == null) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with a compatible window decor layout");
}
- mActionView.setContextView(mUpperContextView);
- mContextDisplayMode = mLowerContextView == null ?
- CONTEXT_DISPLAY_NORMAL : CONTEXT_DISPLAY_SPLIT;
+ mHasEmbeddedTabs = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.action_bar_embed_tabs);
+ mActionView.setContextView(mContextView);
+ mContextDisplayMode = mActionView.isSplitActionBar() ?
+ CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
+
+ mContentHeight = mActionView.getContentHeight();
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ mHasEmbeddedTabs = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.action_bar_embed_tabs);
+
+ // Switch tab layout configuration if needed
+ if (!mHasEmbeddedTabs) {
+ mActionView.setEmbeddedTabView(null);
+ mContainerView.setTabContainer(mTabScrollView);
+ } else {
+ mContainerView.setTabContainer(null);
+ if (mTabScrollView != null) {
+ mTabScrollView.setVisibility(View.VISIBLE);
+ }
+ mActionView.setEmbeddedTabView(mTabScrollView);
+ }
+ mActionView.setCollapsable(!mHasEmbeddedTabs &&
+ getNavigationMode() == NAVIGATION_MODE_TABS);
+
+ mContentHeight = mActionView.getContentHeight();
+
+ if (mTabScrollView != null) {
+ mTabScrollView.getLayoutParams().height = mContentHeight;
+ mTabScrollView.requestLayout();
+ }
+ }
+
+ private void ensureTabsExist() {
+ if (mTabScrollView != null) {
+ return;
+ }
+
+ ScrollingTabContainerView tabScroller = mActionView.createTabContainer();
+
+ if (mHasEmbeddedTabs) {
+ tabScroller.setVisibility(View.VISIBLE);
+ mActionView.setEmbeddedTabView(tabScroller);
+ } else {
+ tabScroller.setVisibility(getNavigationMode() == NAVIGATION_MODE_TABS ?
+ View.VISIBLE : View.GONE);
+ mContainerView.setTabContainer(tabScroller);
+ }
+ mTabScrollView = tabScroller;
}
/**
@@ -229,8 +212,8 @@ public class ActionBarImpl extends ActionBar {
*/
public void setShowHideAnimationEnabled(boolean enabled) {
mShowHideAnimationEnabled = enabled;
- if (!enabled && mCurrentAnim != null) {
- mCurrentAnim.end();
+ if (!enabled && mCurrentShowAnim != null) {
+ mCurrentShowAnim.end();
}
}
@@ -285,6 +268,11 @@ public class ActionBarImpl extends ActionBar {
}
@Override
+ public void setDisplayDisableHomeEnabled(boolean disableHome) {
+ setDisplayOptions(disableHome ? DISPLAY_DISABLE_HOME : 0, DISPLAY_DISABLE_HOME);
+ }
+
+ @Override
public void setTitle(int resId) {
setTitle(mContext.getString(resId));
}
@@ -317,7 +305,9 @@ public class ActionBarImpl extends ActionBar {
selectTab(null);
}
mTabs.clear();
- mActionView.removeAllTabs();
+ if (mTabScrollView != null) {
+ mTabScrollView.removeAllTabs();
+ }
mSavedTabPosition = INVALID_POSITION;
}
@@ -367,18 +357,18 @@ public class ActionBarImpl extends ActionBar {
mActionMode.finish();
}
- mUpperContextView.killMode();
- ActionMode mode = new ActionModeImpl(callback);
- if (callback.onCreateActionMode(mode, mode.getMenu())) {
+ mContextView.killMode();
+ ActionModeImpl mode = new ActionModeImpl(callback);
+ if (mode.dispatchOnCreate()) {
+ mWasHiddenBeforeMode = !isShowing();
mode.invalidate();
- mUpperContextView.initForMode(mode);
- animateTo(CONTEXT_VIEW);
- if (mLowerContextView != null) {
+ mContextView.initForMode(mode);
+ animateToMode(true);
+ if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
// TODO animate this
- mLowerContextView.setVisibility(View.VISIBLE);
+ mSplitView.setVisibility(View.VISIBLE);
}
mActionMode = mode;
- show();
return mode;
}
return null;
@@ -413,7 +403,8 @@ public class ActionBarImpl extends ActionBar {
@Override
public void addTab(Tab tab, boolean setSelected) {
- mActionView.addTab(tab, setSelected);
+ ensureTabsExist();
+ mTabScrollView.addTab(tab, setSelected);
configureTab(tab, mTabs.size());
if (setSelected) {
selectTab(tab);
@@ -422,7 +413,8 @@ public class ActionBarImpl extends ActionBar {
@Override
public void addTab(Tab tab, int position, boolean setSelected) {
- mActionView.addTab(tab, position, setSelected);
+ ensureTabsExist();
+ mTabScrollView.addTab(tab, position, setSelected);
configureTab(tab, position);
if (setSelected) {
selectTab(tab);
@@ -441,10 +433,18 @@ public class ActionBarImpl extends ActionBar {
@Override
public void removeTabAt(int position) {
+ if (mTabScrollView == null) {
+ // No tabs around to remove
+ return;
+ }
+
int selectedTabPosition = mSelectedTab != null
? mSelectedTab.getPosition() : mSavedTabPosition;
- mActionView.removeTabAt(position);
- mTabs.remove(position);
+ mTabScrollView.removeTabAt(position);
+ TabImpl removedTab = mTabs.remove(position);
+ if (removedTab != null) {
+ removedTab.setPosition(-1);
+ }
final int newTabCount = mTabs.size();
for (int i = position; i < newTabCount; i++) {
@@ -469,9 +469,10 @@ public class ActionBarImpl extends ActionBar {
if (mSelectedTab == tab) {
if (mSelectedTab != null) {
mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans);
+ mTabScrollView.animateToTab(tab.getPosition());
}
} else {
- mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
+ mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
if (mSelectedTab != null) {
mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
}
@@ -498,10 +499,15 @@ public class ActionBarImpl extends ActionBar {
@Override
public void show() {
- if (mCurrentAnim != null) {
- mCurrentAnim.end();
+ show(true);
+ }
+
+ void show(boolean markHiddenBeforeMode) {
+ if (mCurrentShowAnim != null) {
+ mCurrentShowAnim.end();
}
if (mContainerView.getVisibility() == View.VISIBLE) {
+ if (markHiddenBeforeMode) mWasHiddenBeforeMode = false;
return;
}
mContainerView.setVisibility(View.VISIBLE);
@@ -516,18 +522,24 @@ public class ActionBarImpl extends ActionBar {
mContainerView.setTranslationY(-mContainerView.getHeight());
b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", 0));
}
+ if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
+ mSplitView.setAlpha(0);
+ b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 1));
+ }
anim.addListener(mShowListener);
- mCurrentAnim = anim;
+ mCurrentShowAnim = anim;
anim.start();
} else {
+ mContainerView.setAlpha(1);
+ mContainerView.setTranslationY(0);
mShowListener.onAnimationEnd(null);
}
}
@Override
public void hide() {
- if (mCurrentAnim != null) {
- mCurrentAnim.end();
+ if (mCurrentShowAnim != null) {
+ mCurrentShowAnim.end();
}
if (mContainerView.getVisibility() == View.GONE) {
return;
@@ -544,8 +556,12 @@ public class ActionBarImpl extends ActionBar {
b.with(ObjectAnimator.ofFloat(mContainerView, "translationY",
-mContainerView.getHeight()));
}
+ if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) {
+ mSplitView.setAlpha(1);
+ b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 0));
+ }
anim.addListener(mHideListener);
- mCurrentAnim = anim;
+ mCurrentShowAnim = anim;
anim.start();
} else {
mHideListener.onAnimationEnd(null);
@@ -556,41 +572,14 @@ public class ActionBarImpl extends ActionBar {
return mContainerView.getVisibility() == View.VISIBLE;
}
- private long animateTo(int viewIndex) {
- show();
-
- AnimatorSet set = new AnimatorSet();
-
- final View targetChild = mContainerView.getChildAt(viewIndex);
- targetChild.setVisibility(View.VISIBLE);
- AnimatorSet.Builder b = set.play(ObjectAnimator.ofFloat(targetChild, "alpha", 1));
-
- final int count = mContainerView.getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = mContainerView.getChildAt(i);
- if (i == viewIndex) {
- continue;
- }
-
- if (child.getVisibility() != View.GONE) {
- Animator a = ObjectAnimator.ofFloat(child, "alpha", 0);
- a.setInterpolator(sFadeOutInterpolator);
- b.with(a);
- }
+ void animateToMode(boolean toActionMode) {
+ show(false);
+ if (mCurrentModeAnim != null) {
+ mCurrentModeAnim.end();
}
- set.addListener(mAfterAnimation[viewIndex]);
-
- mCurrentAnim = set;
- set.start();
- return set.getDuration();
- }
-
- private void hideAllExcept(int viewIndex) {
- final int count = mContainerView.getChildCount();
- for (int i = 0; i < count; i++) {
- mContainerView.getChildAt(i).setVisibility(i == viewIndex ? View.VISIBLE : View.GONE);
- }
+ mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
+ mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
}
/**
@@ -627,38 +616,50 @@ public class ActionBarImpl extends ActionBar {
mCallback.onDestroyActionMode(this);
mCallback = null;
- animateTo(NORMAL_VIEW);
+ animateToMode(false);
// Clear out the context mode views after the animation finishes
- mUpperContextView.closeMode();
- if (mLowerContextView != null && mLowerContextView.getVisibility() != View.GONE) {
- // TODO Animate this
- mLowerContextView.setVisibility(View.GONE);
- }
+ mContextView.closeMode();
mActionMode = null;
+
+ if (mWasHiddenBeforeMode) {
+ hide();
+ }
}
@Override
public void invalidate() {
- if (mCallback.onPrepareActionMode(this, mMenu)) {
- // Refresh content in both context views
+ mMenu.stopDispatchingItemsChanged();
+ try {
+ mCallback.onPrepareActionMode(this, mMenu);
+ } finally {
+ mMenu.startDispatchingItemsChanged();
+ }
+ }
+
+ public boolean dispatchOnCreate() {
+ mMenu.stopDispatchingItemsChanged();
+ try {
+ return mCallback.onCreateActionMode(this, mMenu);
+ } finally {
+ mMenu.startDispatchingItemsChanged();
}
}
@Override
public void setCustomView(View view) {
- mUpperContextView.setCustomView(view);
+ mContextView.setCustomView(view);
mCustomView = new WeakReference<View>(view);
}
@Override
public void setSubtitle(CharSequence subtitle) {
- mUpperContextView.setSubtitle(subtitle);
+ mContextView.setSubtitle(subtitle);
}
@Override
public void setTitle(CharSequence title) {
- mUpperContextView.setTitle(title);
+ mContextView.setTitle(title);
}
@Override
@@ -673,12 +674,12 @@ public class ActionBarImpl extends ActionBar {
@Override
public CharSequence getTitle() {
- return mUpperContextView.getTitle();
+ return mContextView.getTitle();
}
@Override
public CharSequence getSubtitle() {
- return mUpperContextView.getSubtitle();
+ return mContextView.getSubtitle();
}
@Override
@@ -718,7 +719,7 @@ public class ActionBarImpl extends ActionBar {
return;
}
invalidate();
- mUpperContextView.openOverflowMenu();
+ mContextView.showOverflowMenu();
}
}
@@ -730,7 +731,7 @@ public class ActionBarImpl extends ActionBar {
private Object mTag;
private Drawable mIcon;
private CharSequence mText;
- private int mPosition;
+ private int mPosition = -1;
private View mCustomView;
@Override
@@ -762,6 +763,9 @@ public class ActionBarImpl extends ActionBar {
@Override
public Tab setCustomView(View view) {
mCustomView = view;
+ if (mPosition >= 0) {
+ mTabScrollView.updateTab(mPosition);
+ }
return this;
}
@@ -792,6 +796,9 @@ public class ActionBarImpl extends ActionBar {
@Override
public Tab setIcon(Drawable icon) {
mIcon = icon;
+ if (mPosition >= 0) {
+ mTabScrollView.updateTab(mPosition);
+ }
return this;
}
@@ -803,6 +810,9 @@ public class ActionBarImpl extends ActionBar {
@Override
public Tab setText(CharSequence text) {
mText = text;
+ if (mPosition >= 0) {
+ mTabScrollView.updateTab(mPosition);
+ }
return this;
}
@@ -871,17 +881,25 @@ public class ActionBarImpl extends ActionBar {
case NAVIGATION_MODE_TABS:
mSavedTabPosition = getSelectedNavigationIndex();
selectTab(null);
+ if (!mActionView.hasEmbeddedTabs()) {
+ mTabScrollView.setVisibility(View.GONE);
+ }
break;
}
mActionView.setNavigationMode(mode);
switch (mode) {
case NAVIGATION_MODE_TABS:
+ ensureTabsExist();
+ if (!mActionView.hasEmbeddedTabs()) {
+ mTabScrollView.setVisibility(View.VISIBLE);
+ }
if (mSavedTabPosition != INVALID_POSITION) {
setSelectedNavigationItem(mSavedTabPosition);
mSavedTabPosition = INVALID_POSITION;
}
break;
}
+ mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
}
@Override
@@ -889,23 +907,24 @@ public class ActionBarImpl extends ActionBar {
return mTabs.get(index);
}
- /**
- * This fragment is added when we're keeping a back stack in a tab switch
- * transaction. We use it to change the selected tab in the action bar view
- * when we back out.
- */
- private class SwitchSelectedTabViewFragment extends Fragment {
- private int mSelectedTabIndex;
- public SwitchSelectedTabViewFragment(int oldSelectedTab) {
- mSelectedTabIndex = oldSelectedTab;
- }
+ @Override
+ public void setIcon(int resId) {
+ mActionView.setIcon(resId);
+ }
- @Override
- public void onDetach() {
- if (mSelectedTabIndex >= 0 && mSelectedTabIndex < getTabCount()) {
- mActionView.setTabSelected(mSelectedTabIndex);
- }
- }
+ @Override
+ public void setIcon(Drawable icon) {
+ mActionView.setIcon(icon);
+ }
+
+ @Override
+ public void setLogo(int resId) {
+ mActionView.setLogo(resId);
+ }
+
+ @Override
+ public void setLogo(Drawable logo) {
+ mActionView.setLogo(logo);
}
}
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index aee1626..dd22e25 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -27,8 +27,9 @@ interface IMediaContainerService {
String key, String resFileName);
boolean copyResource(in Uri packageURI,
in ParcelFileDescriptor outStream);
- PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags);
- boolean checkFreeStorage(boolean external, in Uri fileUri);
+ PackageInfoLite getMinimalPackageInfo(in Uri fileUri, in int flags, in long threshold);
+ boolean checkInternalFreeStorage(in Uri fileUri, in long threshold);
+ boolean checkExternalFreeStorage(in Uri fileUri);
ObbInfo getObbInfo(in String filename);
long calculateDirectorySize(in String directory);
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 2e56996..ba2f5d4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -30,7 +30,6 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.PatternMatcher;
-import android.util.Config;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -238,7 +237,7 @@ public class ResolverActivity extends AlertActivity implements
ResolveInfo r0 = rList.get(0);
for (int i=1; i<N; i++) {
ResolveInfo ri = rList.get(i);
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
"ResolveListActivity",
r0.activityInfo.name + "=" +
r0.priority + "/" + r0.isDefault + " vs " +
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 4ae55fc..9ae7def 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -4,7 +4,6 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.FileUtils;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -176,7 +175,7 @@ public class NativeLibraryHelper {
continue;
}
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "Found gdbserver: " + entry.getName());
}
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index d6c43f9..b57046c 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -56,18 +56,13 @@ public class PackageHelper {
return null;
}
- public static String createSdDir(long sizeBytes, String cid,
+ public static String createSdDir(int sizeMb, String cid,
String sdEncKey, int uid) {
// Create mount point via MountService
IMountService mountService = getMountService();
- int sizeMb = (int) (sizeBytes >> 20);
- if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
- sizeMb++;
- }
- // Add buffer size
- sizeMb++;
+
if (localLOGV)
- Log.i(TAG, "Size of container " + sizeMb + " MB " + sizeBytes + " bytes");
+ Log.i(TAG, "Size of container " + sizeMb + " MB");
try {
int rc = mountService.createSecureContainer(
diff --git a/core/java/com/android/internal/net/DomainNameValidator.java b/core/java/com/android/internal/net/DomainNameValidator.java
index 36973f1..3950655 100644
--- a/core/java/com/android/internal/net/DomainNameValidator.java
+++ b/core/java/com/android/internal/net/DomainNameValidator.java
@@ -16,7 +16,6 @@
package com.android.internal.net;
import android.net.NetworkUtils;
-import android.util.Config;
import android.util.Log;
import java.net.InetAddress;
@@ -35,7 +34,7 @@ public class DomainNameValidator {
private final static String TAG = "DomainNameValidator";
private static final boolean DEBUG = false;
- private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOG_ENABLED = false;
private static final int ALT_DNS_NAME = 2;
private static final int ALT_IPA_NAME = 7;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 2847cf3..12687a1 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -36,6 +36,7 @@ import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.util.LogWriter;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
@@ -70,7 +71,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 54;
+ private static final int VERSION = 60;
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -154,11 +155,27 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mHaveBatteryLevel = false;
boolean mRecordingHistory = true;
int mNumHistoryItems;
+
+ static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB
+ static final int MAX_MAX_HISTORY_BUFFER = 144*1024; // 144KB
+ final Parcel mHistoryBuffer = Parcel.obtain();
+ final HistoryItem mHistoryLastWritten = new HistoryItem();
+ final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+ final HistoryItem mHistoryReadTmp = new HistoryItem();
+ int mHistoryBufferLastPos = -1;
+ boolean mHistoryOverflow = false;
+ long mLastHistoryTime = 0;
+
+ final HistoryItem mHistoryCur = new HistoryItem();
+
HistoryItem mHistory;
HistoryItem mHistoryEnd;
HistoryItem mHistoryLastEnd;
HistoryItem mHistoryCache;
- final HistoryItem mHistoryCur = new HistoryItem();
+
+ private HistoryItem mHistoryIterator;
+ private boolean mReadOverflow;
+ private boolean mIteratingHistory;
int mStartCount;
@@ -1189,9 +1206,84 @@ public final class BatteryStatsImpl extends BatteryStats {
mBtHeadset = headset;
}
+ int mChangedBufferStates = 0;
+
+ void addHistoryBufferLocked(long curTime) {
+ if (!mHaveBatteryLevel || !mRecordingHistory) {
+ return;
+ }
+
+ final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time;
+ if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+ && timeDiff < 2000
+ && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) {
+ // If the current is the same as the one before, then we no
+ // longer need the entry.
+ mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+ mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+ mHistoryBufferLastPos = -1;
+ if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE
+ && timeDiff < 500 && mHistoryLastLastWritten.same(mHistoryCur)) {
+ // If this results in us returning to the state written
+ // prior to the last one, then we can just delete the last
+ // written one and drop the new one. Nothing more to do.
+ mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+ mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
+ return;
+ }
+ mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states;
+ curTime = mHistoryLastWritten.time - mHistoryBaseTime;
+ mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+ } else {
+ mChangedBufferStates = 0;
+ }
+
+ final int dataSize = mHistoryBuffer.dataSize();
+ if (dataSize >= MAX_HISTORY_BUFFER) {
+ if (!mHistoryOverflow) {
+ mHistoryOverflow = true;
+ addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW);
+ }
+
+ // Once we've reached the maximum number of items, we only
+ // record changes to the battery level and the most interesting states.
+ // Once we've reached the maximum maximum number of items, we only
+ // record changes to the battery level.
+ if (mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel &&
+ (dataSize >= MAX_MAX_HISTORY_BUFFER
+ || ((mHistoryEnd.states^mHistoryCur.states)
+ & HistoryItem.MOST_INTERESTING_STATES) == 0)) {
+ return;
+ }
+ }
+
+ addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
+ }
+
+ void addHistoryBufferLocked(long curTime, byte cmd) {
+ int origPos = 0;
+ if (mIteratingHistory) {
+ origPos = mHistoryBuffer.dataPosition();
+ mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ }
+ mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+ mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+ mHistoryLastWritten.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
+ mHistoryLastWritten.writeDelta(mHistoryBuffer, mHistoryLastLastWritten);
+ mLastHistoryTime = curTime;
+ if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ + " now " + mHistoryBuffer.dataPosition()
+ + " size is now " + mHistoryBuffer.dataSize());
+ if (mIteratingHistory) {
+ mHistoryBuffer.setDataPosition(origPos);
+ }
+ }
+
int mChangedStates = 0;
void addHistoryRecordLocked(long curTime) {
+ addHistoryBufferLocked(curTime);
+
if (!mHaveBatteryLevel || !mRecordingHistory) {
return;
}
@@ -1206,6 +1298,7 @@ public final class BatteryStatsImpl extends BatteryStats {
// If the current is the same as the one before, then we no
// longer need the entry.
if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
+ && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)
&& mHistoryLastEnd.same(mHistoryCur)) {
mHistoryLastEnd.next = null;
mHistoryEnd.next = mHistoryCache;
@@ -1268,6 +1361,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void clearHistoryLocked() {
+ if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
if (mHistory != null) {
mHistoryEnd.next = mHistoryCache;
mHistoryCache = mHistory;
@@ -1275,6 +1369,15 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mNumHistoryItems = 0;
mHistoryBaseTime = 0;
+ mLastHistoryTime = 0;
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2);
+ mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistoryBufferLastPos = -1;
+ mHistoryOverflow = false;
}
public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
@@ -3910,11 +4013,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeUnplugLevel = 0;
mDischargeCurrentLevel = 0;
initDischarge();
+ clearHistoryLocked();
}
public BatteryStatsImpl(Parcel p) {
mFile = null;
mHandler = null;
+ clearHistoryLocked();
readFromParcel(p);
}
@@ -3932,25 +4037,84 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- private HistoryItem mHistoryIterator;
-
- public boolean startIteratingHistoryLocked() {
+ @Override
+ public boolean startIteratingOldHistoryLocked() {
+ if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ + " pos=" + mHistoryBuffer.dataPosition());
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryReadTmp.clear();
+ mReadOverflow = false;
+ mIteratingHistory = true;
return (mHistoryIterator = mHistory) != null;
}
- public boolean getNextHistoryLocked(HistoryItem out) {
+ @Override
+ public boolean getNextOldHistoryLocked(HistoryItem out) {
+ boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize();
+ if (!end) {
+ mHistoryReadTmp.readDelta(mHistoryBuffer);
+ mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW;
+ }
HistoryItem cur = mHistoryIterator;
if (cur == null) {
+ if (!mReadOverflow && !end) {
+ Slog.w(TAG, "Old history ends before new history!");
+ }
return false;
}
out.setTo(cur);
mHistoryIterator = cur.next;
+ if (!mReadOverflow) {
+ if (end) {
+ Slog.w(TAG, "New history ends before old history!");
+ } else if (!out.same(mHistoryReadTmp)) {
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+ PrintWriter pw = new PrintWriter(new LogWriter(android.util.Log.WARN, TAG));
+ pw.println("Histories differ!");
+ pw.println("Old history:");
+ (new HistoryPrinter()).printNextItem(pw, out, now);
+ pw.println("New history:");
+ (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now);
+ }
+ }
return true;
}
@Override
- public HistoryItem getHistory() {
- return mHistory;
+ public void finishIteratingOldHistoryLocked() {
+ mIteratingHistory = false;
+ mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ }
+
+ @Override
+ public boolean startIteratingHistoryLocked() {
+ if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ + " pos=" + mHistoryBuffer.dataPosition());
+ mHistoryBuffer.setDataPosition(0);
+ mReadOverflow = false;
+ mIteratingHistory = true;
+ return mHistoryBuffer.dataSize() > 0;
+ }
+
+ @Override
+ public boolean getNextHistoryLocked(HistoryItem out) {
+ final int pos = mHistoryBuffer.dataPosition();
+ if (pos == 0) {
+ out.clear();
+ }
+ boolean end = pos >= mHistoryBuffer.dataSize();
+ if (end) {
+ return false;
+ }
+
+ out.readDelta(mHistoryBuffer);
+ return true;
+ }
+
+ @Override
+ public void finishIteratingHistoryLocked() {
+ mIteratingHistory = false;
+ mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
}
@Override
@@ -4697,7 +4861,9 @@ public final class BatteryStatsImpl extends BatteryStats {
Slog.e("BatteryStats", "Error reading battery statistics", e);
}
- addHistoryRecordLocked(SystemClock.elapsedRealtime(), HistoryItem.CMD_START);
+ long now = SystemClock.elapsedRealtime();
+ addHistoryRecordLocked(now, HistoryItem.CMD_START);
+ addHistoryBufferLocked(now, HistoryItem.CMD_START);
}
public int describeContents() {
@@ -4705,30 +4871,54 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void readHistory(Parcel in) {
- mHistory = mHistoryEnd = mHistoryCache = null;
- mHistoryBaseTime = 0;
- long time;
- while (in.dataAvail() > 0 && (time=in.readLong()) >= 0) {
- HistoryItem rec = new HistoryItem(time, in);
- addHistoryRecordLocked(rec);
- if (rec.time > mHistoryBaseTime) {
- mHistoryBaseTime = rec.time;
- }
+ mHistoryBaseTime = in.readLong();
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+
+ int bufSize = in.readInt();
+ int curPos = in.dataPosition();
+ if (bufSize >= (MAX_MAX_HISTORY_BUFFER*3)) {
+ Slog.w(TAG, "File corrupt: history data buffer too large " + bufSize);
+ } else if ((bufSize&~3) != bufSize) {
+ Slog.w(TAG, "File corrupt: history data buffer not aligned " + bufSize);
+ } else {
+ if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+ + " bytes at " + curPos);
+ mHistoryBuffer.appendFrom(in, curPos, bufSize);
+ in.setDataPosition(curPos + bufSize);
}
- long oldnow = SystemClock.elapsedRealtime() - (5*60*100);
+ long oldnow = SystemClock.elapsedRealtime() - (5*60*1000);
if (oldnow > 0) {
// If the system process has restarted, but not the entire
// system, then the mHistoryBaseTime already accounts for
// much of the elapsed time. We thus want to adjust it back,
// to avoid large gaps in the data. We determine we are
// in this case by arbitrarily saying it is so if at this
- // point in boot the elapsed time is already more than 5 seconds.
+ // point in boot the elapsed time is already more than 5 minutes.
mHistoryBaseTime -= oldnow;
}
}
+ void readOldHistory(Parcel in) {
+ mHistory = mHistoryEnd = mHistoryCache = null;
+ long time;
+ while (in.dataAvail() > 0 && (time=in.readLong()) >= 0) {
+ HistoryItem rec = new HistoryItem(time, in);
+ addHistoryRecordLocked(rec);
+ }
+ }
+
void writeHistory(Parcel out) {
+ out.writeLong(mLastHistoryTime);
+ out.writeInt(mHistoryBuffer.dataSize());
+ if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
+ + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+ out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ }
+
+ void writeOldHistory(Parcel out) {
HistoryItem rec = mHistory;
while (rec != null) {
if (rec.time >= 0) rec.writeToParcel(out, 0);
@@ -4746,6 +4936,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
readHistory(in);
+ readOldHistory(in);
mStartCount = in.readInt();
mBatteryUptime = in.readLong();
@@ -4935,6 +5126,9 @@ public final class BatteryStatsImpl extends BatteryStats {
* @param out the Parcel to be written to.
*/
public void writeSummaryToParcel(Parcel out) {
+ // Need to update with current kernel wake lock counts.
+ updateKernelWakelocksLocked();
+
final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
final long NOW = getBatteryUptimeLocked(NOW_SYS);
@@ -4943,6 +5137,7 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(VERSION);
writeHistory(out);
+ writeOldHistory(out);
out.writeInt(mStartCount);
out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED));
@@ -5256,6 +5451,9 @@ public final class BatteryStatsImpl extends BatteryStats {
@SuppressWarnings("unused")
void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
+ // Need to update with current kernel wake lock counts.
+ updateKernelWakelocksLocked();
+
final long uSecUptime = SystemClock.uptimeMillis() * 1000;
final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
@@ -5358,6 +5556,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
};
+ public void prepareForDumpLocked() {
+ // Need to retrieve current kernel wake lock stats before printing.
+ updateKernelWakelocksLocked();
+ }
+
public void dumpLocked(PrintWriter pw) {
if (DEBUG) {
Printer pr = new PrintWriterPrinter(pw);
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index ba0bf0d..f54a3e9 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -19,7 +19,6 @@ package com.android.internal.os;
import android.os.Binder;
import android.os.IBinder;
import android.os.SystemClock;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index f58f261..5e9cd23 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -23,7 +23,6 @@ import android.os.Debug;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.Log;
import android.util.Slog;
@@ -33,7 +32,6 @@ import dalvik.system.VMRuntime;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.LogManager;
import java.util.TimeZone;
@@ -46,6 +44,7 @@ import org.apache.harmony.luni.internal.util.TimezoneGetter;
*/
public class RuntimeInit {
private final static String TAG = "AndroidRuntime";
+ private final static boolean DEBUG = false;
/** true if commonInit() has been called */
private static boolean initialized;
@@ -90,14 +89,14 @@ public class RuntimeInit {
}
private static final void commonInit() {
- if (Config.LOGV) Slog.d(TAG, "Entered RuntimeInit!");
+ if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/* set default handler; this applies to all threads in the VM */
Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
int hasQwerty = getQwertyKeyboard();
- if (Config.LOGV) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
+ if (DEBUG) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
if (hasQwerty == 1) {
System.setProperty("qwerty", "1");
}
@@ -184,11 +183,6 @@ public class RuntimeInit {
*/
private static void invokeStaticMain(String className, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
-
- // We want to be fairly aggressive about heap utilization, to avoid
- // holding on to a lot of memory that isn't needed.
- VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
-
Class<?> cl;
try {
@@ -226,6 +220,13 @@ public class RuntimeInit {
}
public static final void main(String[] argv) {
+ if (argv.length == 2 && argv[1].equals("application")) {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
+ redirectLogStreams();
+ } else {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
+ }
+
commonInit();
/*
@@ -234,7 +235,7 @@ public class RuntimeInit {
*/
finishInit();
- if (Config.LOGV) Slog.d(TAG, "Leaving RuntimeInit!");
+ if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
public static final native void finishInit();
@@ -246,7 +247,6 @@ public class RuntimeInit {
*
* Current recognized args:
* <ul>
- * <li> --nice-name=<i>nice name to appear in ps</i>
* <li> <code> [--] &lt;start class name&gt; &lt;args&gt;
* </ul>
*
@@ -254,45 +254,60 @@ public class RuntimeInit {
*/
public static final void zygoteInit(String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
- // TODO: Doing this here works, but it seems kind of arbitrary. Find
- // a better place. The goal is to set it up for applications, but not
- // tools like am.
- System.out.close();
- System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
- System.err.close();
- System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
+
+ redirectLogStreams();
commonInit();
zygoteInitNative();
- int curArg = 0;
- for ( /* curArg */ ; curArg < argv.length; curArg++) {
- String arg = argv[curArg];
-
- if (arg.equals("--")) {
- curArg++;
- break;
- } else if (!arg.startsWith("--")) {
- break;
- } else if (arg.startsWith("--nice-name=")) {
- String niceName = arg.substring(arg.indexOf('=') + 1);
- Process.setArgV0(niceName);
- }
- }
+ applicationInit(argv);
+ }
- if (curArg == argv.length) {
- Slog.e(TAG, "Missing classname argument to RuntimeInit!");
+ /**
+ * The main function called when an application is started through a
+ * wrapper process.
+ *
+ * When the wrapper starts, the runtime starts {@link RuntimeInit#main}
+ * which calls {@link WrapperInit#main} which then calls this method.
+ * So we don't need to call commonInit() here.
+ *
+ * @param argv arg strings
+ */
+ public static void wrapperInit(String[] argv)
+ throws ZygoteInit.MethodAndArgsCaller {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from wrapper");
+
+ applicationInit(argv);
+ }
+
+ private static void applicationInit(String[] argv)
+ throws ZygoteInit.MethodAndArgsCaller {
+ // We want to be fairly aggressive about heap utilization, to avoid
+ // holding on to a lot of memory that isn't needed.
+ VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
+
+ final Arguments args;
+ try {
+ args = new Arguments(argv);
+ } catch (IllegalArgumentException ex) {
+ Slog.e(TAG, ex.getMessage());
// let the process exit
return;
}
// Remaining arguments are passed to the start class's static main
+ invokeStaticMain(args.startClass, args.startArgs);
+ }
- String startClass = argv[curArg++];
- String[] startArgs = new String[argv.length - curArg];
-
- System.arraycopy(argv, curArg, startArgs, 0, startArgs.length);
- invokeStaticMain(startClass, startArgs);
+ /**
+ * Redirect System.out and System.err to the Android log.
+ */
+ public static void redirectLogStreams() {
+ System.out.close();
+ System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+ System.err.close();
+ System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
public static final native void zygoteInitNative();
@@ -352,4 +367,55 @@ public class RuntimeInit {
// Register handlers for DDM messages.
android.ddm.DdmRegister.registerHandlers();
}
+
+ /**
+ * Handles argument parsing for args related to the runtime.
+ *
+ * Current recognized args:
+ * <ul>
+ * <li> <code> [--] &lt;start class name&gt; &lt;args&gt;
+ * </ul>
+ */
+ static class Arguments {
+ /** first non-option argument */
+ String startClass;
+
+ /** all following arguments */
+ String[] startArgs;
+
+ /**
+ * Constructs instance and parses args
+ * @param args runtime command-line args
+ * @throws IllegalArgumentException
+ */
+ Arguments(String args[]) throws IllegalArgumentException {
+ parseArgs(args);
+ }
+
+ /**
+ * Parses the commandline arguments intended for the Runtime.
+ */
+ private void parseArgs(String args[])
+ throws IllegalArgumentException {
+ int curArg = 0;
+ for (; curArg < args.length; curArg++) {
+ String arg = args[curArg];
+
+ if (arg.equals("--")) {
+ curArg++;
+ break;
+ } else if (!arg.startsWith("--")) {
+ break;
+ }
+ }
+
+ if (curArg == args.length) {
+ throw new IllegalArgumentException("Missing classname argument to RuntimeInit!");
+ }
+
+ startClass = args[curArg++];
+ startArgs = new String[args.length - curArg];
+ System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
index 8c256e0..df0fcd9 100644
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -20,12 +20,15 @@ import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.SystemProperties;
import android.util.Log;
-import dalvik.system.SamplingProfiler;
+import dalvik.system.profiler.BinaryHprofWriter;
+import dalvik.system.profiler.SamplingProfiler;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintStream;
+import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@@ -80,7 +83,8 @@ public class SamplingProfilerIntegration {
}
}
- private static SamplingProfiler INSTANCE;
+ private static SamplingProfiler samplingProfiler;
+ private static long startMillis;
/**
* Is profiling enabled?
@@ -96,10 +100,16 @@ public class SamplingProfilerIntegration {
if (!enabled) {
return;
}
+ if (samplingProfiler != null) {
+ Log.e(TAG, "SamplingProfilerIntegration already started at " + new Date(startMillis));
+ return;
+ }
+
ThreadGroup group = Thread.currentThread().getThreadGroup();
SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupTheadSet(group);
- INSTANCE = new SamplingProfiler(samplingProfilerDepth, threadSet);
- INSTANCE.start(samplingProfilerMilliseconds);
+ samplingProfiler = new SamplingProfiler(samplingProfilerDepth, threadSet);
+ samplingProfiler.start(samplingProfilerMilliseconds);
+ startMillis = System.currentTimeMillis();
}
/**
@@ -109,6 +119,10 @@ public class SamplingProfilerIntegration {
if (!enabled) {
return;
}
+ if (samplingProfiler == null) {
+ Log.e(TAG, "SamplingProfilerIntegration is not started");
+ return;
+ }
/*
* If we're already writing a snapshot, don't bother enqueueing another
@@ -137,8 +151,9 @@ public class SamplingProfilerIntegration {
return;
}
writeSnapshotFile("zygote", null);
- INSTANCE.shutdown();
- INSTANCE = null;
+ samplingProfiler.shutdown();
+ samplingProfiler = null;
+ startMillis = 0;
}
/**
@@ -148,40 +163,44 @@ public class SamplingProfilerIntegration {
if (!enabled) {
return;
}
- INSTANCE.stop();
+ samplingProfiler.stop();
/*
- * We use the current time as a unique ID. We can't use a counter
- * because processes restart. This could result in some overlap if
- * we capture two snapshots in rapid succession.
+ * We use the global start time combined with the process name
+ * as a unique ID. We can't use a counter because processes
+ * restart. This could result in some overlap if we capture
+ * two snapshots in rapid succession.
*/
- long start = System.currentTimeMillis();
String name = processName.replaceAll(":", ".");
- String path = SNAPSHOT_DIR + "/" + name + "-" +System.currentTimeMillis() + ".snapshot";
- PrintStream out = null;
+ String path = SNAPSHOT_DIR + "/" + name + "-" + startMillis + ".snapshot";
+ long start = System.currentTimeMillis();
+ OutputStream outputStream = null;
try {
- out = new PrintStream(new BufferedOutputStream(new FileOutputStream(path)));
+ outputStream = new BufferedOutputStream(new FileOutputStream(path));
+ PrintStream out = new PrintStream(outputStream);
generateSnapshotHeader(name, packageInfo, out);
- new SamplingProfiler.AsciiHprofWriter(INSTANCE.getHprofData(), out).write();
if (out.checkError()) {
throw new IOException();
}
+ BinaryHprofWriter.write(samplingProfiler.getHprofData(), outputStream);
} catch (IOException e) {
Log.e(TAG, "Error writing snapshot to " + path, e);
return;
} finally {
- IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(outputStream);
}
// set file readable to the world so that SamplingProfilerService
// can put it to dropbox
new File(path).setReadable(true, false);
long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "Wrote snapshot for " + name + " in " + elapsed + "ms.");
+ Log.i(TAG, "Wrote snapshot " + path + " in " + elapsed + "ms.");
+ samplingProfiler.start(samplingProfilerMilliseconds);
}
/**
- * generate header for snapshots, with the following format (like http header):
+ * generate header for snapshots, with the following format
+ * (like an HTTP header but without the \r):
*
* Version: <version number of profiler>\n
* Process: <process name>\n
@@ -194,7 +213,7 @@ public class SamplingProfilerIntegration {
private static void generateSnapshotHeader(String processName, PackageInfo packageInfo,
PrintStream out) {
// profiler version
- out.println("Version: 2");
+ out.println("Version: 3");
out.println("Process: " + processName);
if (packageInfo != null) {
out.println("Package: " + packageInfo.packageName);
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
new file mode 100644
index 0000000..18d6caa
--- /dev/null
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.Process;
+import android.util.Slog;
+
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+import dalvik.system.Zygote;
+
+/**
+ * Startup class for the wrapper process.
+ * @hide
+ */
+public class WrapperInit {
+ private final static String TAG = "AndroidRuntime";
+
+ /**
+ * Class not instantiable.
+ */
+ private WrapperInit() {
+ }
+
+ /**
+ * The main function called when starting a runtime application through a
+ * wrapper process instead of by forking Zygote.
+ *
+ * The first argument specifies the file descriptor for a pipe that should receive
+ * the pid of this process, or 0 if none. The remaining arguments are passed to
+ * the runtime.
+ *
+ * @param args The command-line arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ int fdNum = Integer.parseInt(args[0], 10);
+ if (fdNum != 0) {
+ try {
+ FileDescriptor fd = ZygoteInit.createFileDescriptor(fdNum);
+ DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+ os.writeInt(Process.myPid());
+ os.close();
+ IoUtils.closeQuietly(fd);
+ } catch (IOException ex) {
+ Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
+ }
+ }
+
+ String[] runtimeArgs = new String[args.length - 1];
+ System.arraycopy(args, 1, runtimeArgs, 0, runtimeArgs.length);
+ RuntimeInit.wrapperInit(runtimeArgs);
+ } catch (ZygoteInit.MethodAndArgsCaller caller) {
+ caller.run();
+ }
+ }
+
+ /**
+ * Executes a runtime application with a wrapper command.
+ * This method never returns.
+ *
+ * @param invokeWith The wrapper command.
+ * @param niceName The nice name for the application, or null if none.
+ * @param pipeFd The pipe to which the application's pid should be written, or null if none.
+ * @param args Arguments for {@link RuntimeInit.main}.
+ */
+ public static void execApplication(String invokeWith, String niceName,
+ FileDescriptor pipeFd, String[] args) {
+ StringBuilder command = new StringBuilder(invokeWith);
+ command.append(" /system/bin/app_process /system/bin --application");
+ if (niceName != null) {
+ command.append(" '--nice-name=").append(niceName).append("'");
+ }
+ command.append(" com.android.internal.os.WrapperInit ");
+ command.append(pipeFd != null ? pipeFd.getInt$() : 0);
+ Zygote.appendQuotedShellArgs(command, args);
+ Zygote.execShell(command.toString());
+ }
+
+ /**
+ * Executes a standalone application with a wrapper command.
+ * This method never returns.
+ *
+ * @param invokeWith The wrapper command.
+ * @param classPath The class path.
+ * @param className The class name to invoke.
+ * @param args Arguments for the main() method of the specified class.
+ */
+ public static void execStandalone(String invokeWith, String classPath, String className,
+ String[] args) {
+ StringBuilder command = new StringBuilder(invokeWith);
+ command.append(" /system/bin/dalvikvm -classpath '").append(classPath);
+ command.append("' ").append(className);
+ Zygote.appendQuotedShellArgs(command, args);
+ Zygote.execShell(command.toString());
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index c473fd2..b872e22 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -26,14 +26,20 @@ import dalvik.system.PathClassLoader;
import dalvik.system.Zygote;
import java.io.BufferedReader;
+import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
/**
* A connection that can make spawn requests.
*/
@@ -193,15 +199,20 @@ class ZygoteConnection {
new FileOutputStream(descriptors[2]));
}
- int pid;
+ int pid = -1;
+ FileDescriptor childPipeFd = null;
+ FileDescriptor serverPipeFd = null;
try {
parsedArgs = new Arguments(args);
applyUidSecurityPolicy(parsedArgs, peer);
- applyDebuggerSecurityPolicy(parsedArgs);
applyRlimitSecurityPolicy(parsedArgs, peer);
applyCapabilitiesSecurityPolicy(parsedArgs, peer);
+ applyInvokeWithSecurityPolicy(parsedArgs, peer);
+
+ applyDebuggerSystemProperty(parsedArgs);
+ applyInvokeWithSystemProperty(parsedArgs);
int[][] rlimits = null;
@@ -209,25 +220,45 @@ class ZygoteConnection {
rlimits = parsedArgs.rlimits.toArray(intArray2d);
}
+ if (parsedArgs.runtimeInit && parsedArgs.invokeWith != null) {
+ FileDescriptor[] pipeFds = Libcore.os.pipe();
+ childPipeFd = pipeFds[1];
+ serverPipeFd = pipeFds[0];
+ ZygoteInit.setCloseOnExec(serverPipeFd, true);
+ }
+
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids, parsedArgs.debugFlags, rlimits);
+ } catch (IOException ex) {
+ logAndPrintError(newStderr, "Exception creating pipe", ex);
+ } catch (ErrnoException ex) {
+ logAndPrintError(newStderr, "Exception creating pipe", ex);
} catch (IllegalArgumentException ex) {
- logAndPrintError (newStderr, "Invalid zygote arguments", ex);
- pid = -1;
+ logAndPrintError(newStderr, "Invalid zygote arguments", ex);
} catch (ZygoteSecurityException ex) {
logAndPrintError(newStderr,
"Zygote security policy prevents request: ", ex);
- pid = -1;
}
- if (pid == 0) {
- // in child
- handleChildProc(parsedArgs, descriptors, newStderr);
- // should never happen
- return true;
- } else { /* pid != 0 */
- // in parent...pid of < 0 means failure
- return handleParentProc(pid, descriptors, parsedArgs);
+ try {
+ if (pid == 0) {
+ // in child
+ IoUtils.closeQuietly(serverPipeFd);
+ serverPipeFd = null;
+ handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
+
+ // should never get here, the child is expected to either
+ // throw ZygoteInit.MethodAndArgsCaller or exec().
+ return true;
+ } else {
+ // in parent...pid of < 0 means failure
+ IoUtils.closeQuietly(childPipeFd);
+ childPipeFd = null;
+ return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
+ }
+ } finally {
+ IoUtils.closeQuietly(childPipeFd);
+ IoUtils.closeQuietly(serverPipeFd);
}
}
@@ -244,8 +275,8 @@ class ZygoteConnection {
}
/**
- * Handles argument parsing for args related to the zygote spawner.<p>
-
+ * Handles argument parsing for args related to the zygote spawner.
+ *
* Current recognized args:
* <ul>
* <li> --setuid=<i>uid of child process, defaults to 0</i>
@@ -274,6 +305,7 @@ class ZygoteConnection {
* be handed off to com.android.internal.os.RuntimeInit, rather than
* processed directly
* Android runtime startup (eg, Binder initialization) is also eschewed.
+ * <li> --nice-name=<i>nice name to appear in ps</i>
* <li> If <code>--runtime-init</code> is present:
* [--] &lt;args for RuntimeInit &gt;
* <li> If <code>--runtime-init</code> is absent:
@@ -307,6 +339,9 @@ class ZygoteConnection {
/** from --runtime-init */
boolean runtimeInit;
+ /** from --nice-name */
+ String niceName;
+
/** from --capabilities */
boolean capabilitiesSpecified;
long permittedCapabilities;
@@ -315,6 +350,9 @@ class ZygoteConnection {
/** from all --rlimit=r,c,m */
ArrayList<int[]> rlimits;
+ /** from --invoke-with */
+ String invokeWith;
+
/**
* Any args after and including the first non-option arg
* (or after a '--')
@@ -438,6 +476,23 @@ class ZygoteConnection {
for (int i = params.length - 1; i >= 0 ; i--) {
gids[i] = Integer.parseInt(params[i]);
}
+ } else if (arg.equals("--invoke-with")) {
+ if (invokeWith != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ try {
+ invokeWith = args[++curArg];
+ } catch (IndexOutOfBoundsException ex) {
+ throw new IllegalArgumentException(
+ "--invoke-with requires argument");
+ }
+ } else if (arg.startsWith("--nice-name=")) {
+ if (niceName != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ niceName = arg.substring(arg.indexOf('=') + 1);
} else {
break;
}
@@ -567,14 +622,15 @@ class ZygoteConnection {
/**
- * Applies debugger security policy.
+ * Applies debugger system properties to the zygote arguments.
+ *
* If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
* the debugger state is specified via the "--enable-debugger" flag
* in the spawn request.
*
* @param args non-null; zygote spawner args
*/
- private static void applyDebuggerSecurityPolicy(Arguments args) {
+ public static void applyDebuggerSystemProperty(Arguments args) {
if ("1".equals(SystemProperties.get("ro.debuggable"))) {
args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
}
@@ -664,12 +720,56 @@ class ZygoteConnection {
}
/**
+ * Applies zygote security policy.
+ * Based on the credentials of the process issuing a zygote command:
+ * <ol>
+ * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
+ * wrapper command.
+ * <li> Any other uid may not specify any invoke-with argument.
+ * </ul>
+ *
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException
+ */
+ private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
+ throws ZygoteSecurityException {
+ int peerUid = peer.getUid();
+
+ if (args.invokeWith != null && peerUid != 0) {
+ throw new ZygoteSecurityException("Peer is not permitted to specify "
+ + "an explicit invoke-with wrapper command");
+ }
+ }
+
+ /**
+ * Applies invoke-with system properties to the zygote arguments.
+ *
+ * @param parsedArgs non-null; zygote args
+ */
+ public static void applyInvokeWithSystemProperty(Arguments args) {
+ if (args.invokeWith == null && args.niceName != null) {
+ if (args.niceName != null) {
+ String property = "wrap." + args.niceName;
+ if (property.length() > 31) {
+ property = property.substring(0, 31);
+ }
+ args.invokeWith = SystemProperties.get(property);
+ if (args.invokeWith != null && args.invokeWith.length() == 0) {
+ args.invokeWith = null;
+ }
+ }
+ }
+ }
+
+ /**
* Handles post-fork setup of child proc, closing sockets as appropriate,
* reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
* if successful or returning if failed.
*
* @param parsedArgs non-null; zygote args
* @param descriptors null-ok; new file descriptors for stdio if available.
+ * @param pipeFd null-ok; pipe for communication back to Zygote.
* @param newStderr null-ok; stream to use for stderr until stdio
* is reopened.
*
@@ -677,7 +777,7 @@ class ZygoteConnection {
* trampoline to code that invokes static main.
*/
private void handleChildProc(Arguments parsedArgs,
- FileDescriptor[] descriptors, PrintStream newStderr)
+ FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
/*
@@ -704,7 +804,7 @@ class ZygoteConnection {
descriptors[1], descriptors[2]);
for (FileDescriptor fd: descriptors) {
- ZygoteInit.closeDescriptor(fd);
+ IoUtils.closeQuietly(fd);
}
newStderr = System.err;
} catch (IOException ex) {
@@ -712,37 +812,48 @@ class ZygoteConnection {
}
}
- if (parsedArgs.runtimeInit) {
- RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
- } else {
- ClassLoader cloader;
+ if (parsedArgs.niceName != null) {
+ Process.setArgV0(parsedArgs.niceName);
+ }
- if (parsedArgs.classpath != null) {
- cloader
- = new PathClassLoader(parsedArgs.classpath,
- ClassLoader.getSystemClassLoader());
+ if (parsedArgs.runtimeInit) {
+ if (parsedArgs.invokeWith != null) {
+ WrapperInit.execApplication(parsedArgs.invokeWith,
+ parsedArgs.niceName, pipeFd, parsedArgs.remainingArgs);
} else {
- cloader = ClassLoader.getSystemClassLoader();
+ RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
}
-
+ } else {
String className;
try {
className = parsedArgs.remainingArgs[0];
} catch (ArrayIndexOutOfBoundsException ex) {
- logAndPrintError (newStderr,
+ logAndPrintError(newStderr,
"Missing required class name argument", null);
return;
}
- String[] mainArgs
- = new String[parsedArgs.remainingArgs.length - 1];
+ String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];
System.arraycopy(parsedArgs.remainingArgs, 1,
mainArgs, 0, mainArgs.length);
- try {
- ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
- } catch (RuntimeException ex) {
- logAndPrintError (newStderr, "Error starting. ", ex);
+ if (parsedArgs.invokeWith != null) {
+ WrapperInit.execStandalone(parsedArgs.invokeWith,
+ parsedArgs.classpath, className, mainArgs);
+ } else {
+ ClassLoader cloader;
+ if (parsedArgs.classpath != null) {
+ cloader = new PathClassLoader(parsedArgs.classpath,
+ ClassLoader.getSystemClassLoader());
+ } else {
+ cloader = ClassLoader.getSystemClassLoader();
+ }
+
+ try {
+ ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
+ } catch (RuntimeException ex) {
+ logAndPrintError(newStderr, "Error starting.", ex);
+ }
}
}
}
@@ -754,36 +865,54 @@ class ZygoteConnection {
* if &lt; 0;
* @param descriptors null-ok; file descriptors for child's new stdio if
* specified.
+ * @param pipeFd null-ok; pipe for communication with child.
* @param parsedArgs non-null; zygote args
* @return true for "exit command loop" and false for "continue command
* loop"
*/
private boolean handleParentProc(int pid,
- FileDescriptor[] descriptors, Arguments parsedArgs) {
+ FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
+
+ if (pid > 0) {
+ setChildPgid(pid);
+ }
+
+ if (descriptors != null) {
+ for (FileDescriptor fd: descriptors) {
+ IoUtils.closeQuietly(fd);
+ }
+ }
- if(pid > 0) {
- // Try to move the new child into the peer's process group.
+ if (pipeFd != null && pid > 0) {
+ DataInputStream is = new DataInputStream(new FileInputStream(pipeFd));
+ int innerPid = -1;
try {
- ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
+ innerPid = is.readInt();
} catch (IOException ex) {
- // This exception is expected in the case where
- // the peer is not in our session
- // TODO get rid of this log message in the case where
- // getsid(0) != getsid(peer.getPid())
- Log.i(TAG, "Zygote: setpgid failed. This is "
- + "normal if peer is not in our session");
+ Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ex) {
+ }
}
- }
- try {
- if (descriptors != null) {
- for (FileDescriptor fd: descriptors) {
- ZygoteInit.closeDescriptor(fd);
+ // Ensure that the pid reported by the wrapped process is either the
+ // child process that we forked, or a descendant of it.
+ if (innerPid > 0) {
+ int parentPid = innerPid;
+ while (parentPid > 0 && parentPid != pid) {
+ parentPid = Process.getParentPid(parentPid);
+ }
+ if (parentPid > 0) {
+ Log.i(TAG, "Wrapped process has pid " + innerPid);
+ pid = innerPid;
+ } else {
+ Log.w(TAG, "Wrapped process reported a pid that is not a child of "
+ + "the process that we forked: childPid=" + pid
+ + " innerPid=" + innerPid);
}
}
- } catch (IOException ex) {
- Log.e(TAG, "Error closing passed descriptors in "
- + "parent process", ex);
}
try {
@@ -808,6 +937,20 @@ class ZygoteConnection {
return false;
}
+ private void setChildPgid(int pid) {
+ // Try to move the new child into the peer's process group.
+ try {
+ ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
+ } catch (IOException ex) {
+ // This exception is expected in the case where
+ // the peer is not in our session
+ // TODO get rid of this log message in the case where
+ // getsid(0) != getsid(peer.getPid())
+ Log.i(TAG, "Zygote: setpgid failed. This is "
+ + "normal if peer is not in our session");
+ }
+ }
+
/**
* Logs an error message and prints it to the specified stream, if
* provided
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index dea53bf..157c0bf 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -23,15 +23,16 @@ import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
import android.os.FileUtils;
+import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import dalvik.system.VMRuntime;
import dalvik.system.Zygote;
-import dalvik.system.SamplingProfiler;
+
+import libcore.io.IoUtils;
import java.io.BufferedReader;
import java.io.FileDescriptor;
@@ -68,7 +69,7 @@ public class ZygoteInit {
private static final int PRELOAD_GC_THRESHOLD = 50000;
public static final String USAGE_STRING =
- " <\"true\"|\"false\" for startSystemServer>";
+ " <\"start-system-server\"|\"\" for startSystemServer>";
private static LocalServerSocket sServerSocket;
@@ -99,25 +100,6 @@ public class ZygoteInit {
private static final boolean PRELOAD_RESOURCES = true;
/**
- * List of methods we "warm up" in the register map cache. These were
- * chosen because they appeared on the stack in GCs in multiple
- * applications.
- *
- * This is in a VM-ready format, to minimize string processing. If a
- * class is not already loaded, or a method is not found, the entry
- * will be skipped.
- *
- * This doesn't really merit a separately-generated input file at this
- * time. The list is fairly short, and the consequences of failure
- * are minor.
- */
- private static final String[] REGISTER_MAP_METHODS = {
- // (currently not doing any)
- //"Landroid/app/Activity;.setContentView:(I)V",
- };
-
-
- /**
* Invokes a static "main(argv[]) method on class "className".
* Converts various failing exceptions into RuntimeExceptions, with
* the assumption that they will then cause the VM instance to exit.
@@ -274,7 +256,7 @@ public class ZygoteInit {
runtime.setTargetHeapUtilization(0.8f);
// Start with a clean slate.
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.startAllocCounting();
@@ -292,16 +274,16 @@ public class ZygoteInit {
}
try {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Preloading " + line + "...");
}
Class.forName(line);
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG,
" GC at " + Debug.getGlobalAllocSize());
}
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
@@ -325,6 +307,7 @@ public class ZygoteInit {
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
+ IoUtils.closeQuietly(is);
// Restore default.
runtime.setTargetHeapUtilization(defaultUtilization);
@@ -338,45 +321,6 @@ public class ZygoteInit {
}
/**
- * Pre-caches register maps for methods that are commonly used.
- */
- private static void cacheRegisterMaps() {
- String failed = null;
- int failure;
- long startTime = System.nanoTime();
-
- failure = 0;
-
- for (int i = 0; i < REGISTER_MAP_METHODS.length; i++) {
- String str = REGISTER_MAP_METHODS[i];
-
- if (!Debug.cacheRegisterMap(str)) {
- if (failed == null)
- failed = str;
- failure++;
- }
- }
-
- long delta = System.nanoTime() - startTime;
-
- if (failure == REGISTER_MAP_METHODS.length) {
- if (REGISTER_MAP_METHODS.length > 0) {
- Log.i(TAG,
- "Register map caching failed (precise GC not enabled?)");
- }
- return;
- }
-
- Log.i(TAG, "Register map cache: found " +
- (REGISTER_MAP_METHODS.length - failure) + " of " +
- REGISTER_MAP_METHODS.length + " methods in " +
- (delta / 1000000L) + "ms");
- if (failure > 0) {
- Log.i(TAG, " First failure: " + failed);
- }
- }
-
- /**
* Load in commonly used resources, so they can be shared across
* processes.
*
@@ -388,7 +332,7 @@ public class ZygoteInit {
Debug.startAllocCounting();
try {
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
mResources = Resources.getSystem();
mResources.startPreloading();
@@ -421,15 +365,15 @@ public class ZygoteInit {
int N = ar.length();
for (int i=0; i<N; i++) {
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
}
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
int id = ar.getResourceId(i, 0);
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
@@ -444,15 +388,15 @@ public class ZygoteInit {
int N = ar.length();
for (int i=0; i<N; i++) {
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
}
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
int id = ar.getResourceId(i, 0);
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
@@ -478,11 +422,11 @@ public class ZygoteInit {
/* runFinalizationSync() lets finalizers be called in Zygote,
* which doesn't have a HeapWorker thread.
*/
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
}
@@ -498,11 +442,20 @@ public class ZygoteInit {
// set umask to 0077 so new files and directories will default to owner-only permissions.
FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
- /*
- * Pass the remaining arguments to SystemServer.
- * "--nice-name=system_server com.android.server.SystemServer"
- */
- RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
+ if (parsedArgs.niceName != null) {
+ Process.setArgV0(parsedArgs.niceName);
+ }
+
+ if (parsedArgs.invokeWith != null) {
+ WrapperInit.execApplication(parsedArgs.invokeWith,
+ parsedArgs.niceName, null, parsedArgs.remainingArgs);
+ } else {
+ /*
+ * Pass the remaining arguments to SystemServer.
+ */
+ RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
+ }
+
/* should never reach here */
}
@@ -527,20 +480,13 @@ public class ZygoteInit {
try {
parsedArgs = new ZygoteConnection.Arguments(args);
-
- /*
- * Enable debugging of the system process if *either* the command line flags
- * indicate it should be debuggable or the ro.debuggable system property
- * is set to "1"
- */
- int debugFlags = parsedArgs.debugFlags;
- if ("1".equals(SystemProperties.get("ro.debuggable")))
- debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+ ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
+ ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids, debugFlags, null,
+ parsedArgs.gids, parsedArgs.debugFlags, null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
@@ -564,7 +510,6 @@ public class ZygoteInit {
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preloadClasses();
- //cacheRegisterMaps();
preloadResources();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
@@ -580,9 +525,9 @@ public class ZygoteInit {
throw new RuntimeException(argv[0] + USAGE_STRING);
}
- if (argv[1].equals("true")) {
+ if (argv[1].equals("start-system-server")) {
startSystemServer();
- } else if (!argv[1].equals("false")) {
+ } else if (!argv[1].equals("")) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}
@@ -754,15 +699,6 @@ public class ZygoteInit {
FileDescriptor out, FileDescriptor err) throws IOException;
/**
- * Calls close() on a file descriptor
- *
- * @param fd descriptor to close
- * @throws IOException
- */
- static native void closeDescriptor(FileDescriptor fd)
- throws IOException;
-
- /**
* Toggles the close-on-exec flag for the specified file descriptor.
*
* @param fd non-null; file descriptor
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 7f23ed5..7d21489 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -34,5 +34,6 @@ oneway interface IStatusBar
void setMenuKeyVisible(boolean visible);
void setImeWindowStatus(in IBinder token, int vis, int backDisposition);
void setHardKeyboardStatus(boolean available, boolean enabled);
+ void userActivity();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index d6ca426..bfc717b 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -46,4 +46,5 @@ interface IStatusBarService
void onNotificationClear(String pkg, String tag, int id);
void setSystemUiVisibility(int vis);
void setHardKeyboardEnabled(boolean enabled);
+ void userActivity();
}
diff --git a/core/java/com/android/internal/util/Objects.java b/core/java/com/android/internal/util/Objects.java
index 598a079..2664182 100644
--- a/core/java/com/android/internal/util/Objects.java
+++ b/core/java/com/android/internal/util/Objects.java
@@ -16,34 +16,47 @@
package com.android.internal.util;
+import java.util.Arrays;
+
/**
* Object utility methods.
*/
public class Objects {
/**
- * Ensures the given object isn't {@code null}.
+ * Determines whether two possibly-null objects are equal. Returns:
+ *
+ * <ul>
+ * <li>{@code true} if {@code a} and {@code b} are both null.
+ * <li>{@code true} if {@code a} and {@code b} are both non-null and they are
+ * equal according to {@link Object#equals(Object)}.
+ * <li>{@code false} in all other situations.
+ * </ul>
*
- * @return the given object
- * @throws NullPointerException if the object is null
+ * <p>This assumes that any non-null objects passed to this function conform
+ * to the {@code equals()} contract.
*/
- public static <T> T nonNull(T t) {
- if (t == null) {
- throw new NullPointerException();
- }
- return t;
+ public static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
}
/**
- * Ensures the given object isn't {@code null}.
+ * Generates a hash code for multiple values. The hash code is generated by
+ * calling {@link Arrays#hashCode(Object[])}.
*
- * @return the given object
- * @throws NullPointerException if the object is null
+ * <p>This is useful for implementing {@link Object#hashCode()}. For example,
+ * in an object that has three properties, {@code x}, {@code y}, and
+ * {@code z}, one could write:
+ * <pre>
+ * public int hashCode() {
+ * return Objects.hashCode(getX(), getY(), getZ());
+ * }</pre>
+ *
+ * <b>Warning</b>: When a single object is supplied, the returned hash code
+ * does not equal the hash code of that object.
*/
- public static <T> T nonNull(T t, String message) {
- if (t == null) {
- throw new NullPointerException(message);
- }
- return t;
+ public static int hashCode(Object... objects) {
+ return Arrays.hashCode(objects);
}
+
}
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
new file mode 100644
index 0000000..a53a9c0
--- /dev/null
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state.
+ */
+public class Preconditions {
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference) {
+ if (reference == null) {
+ throw new NullPointerException();
+ }
+ return reference;
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference, Object errorMessage) {
+ if (reference == null) {
+ throw new NullPointerException(String.valueOf(errorMessage));
+ }
+ return reference;
+ }
+
+}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c41b2cb..b9948fe 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,8 +16,6 @@
package com.android.internal.view;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -26,8 +24,6 @@ import android.os.RemoteException;
import android.view.DragEvent;
import android.view.IWindow;
import android.view.IWindowSession;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
public class BaseIWindow extends IWindow.Stub {
private IWindowSession mSession;
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index b5df812..c792d78 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -174,7 +174,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
public void performPrivateCommand(String action, Bundle data) {
dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
}
-
+
void dispatchMessage(Message msg) {
// If we are calling this from the main thread, then we can call
// right through. Otherwise, we need to send the message to the
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index e00dd4e..719a24f 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -72,4 +72,5 @@ import com.android.internal.view.IInputContextCallback;
void setComposingRegion(int start, int end);
void getSelectedText(int flags, int seq, IInputContextCallback callback);
+
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 611d987..812f92b 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,6 +17,7 @@
package com.android.internal.view;
import android.os.ResultReceiver;
+import android.text.style.SuggestionSpan;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
@@ -33,6 +34,7 @@ interface IInputMethodManager {
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes);
+ InputMethodSubtype getLastInputMethodSubtype();
// TODO: We should change the return type from List to List<Parcelable>
// Currently there is a bug that aidl doesn't accept List<Parcelable>
List getShortcutInputMethodsAndSubtypes();
@@ -60,8 +62,11 @@ interface IInputMethodManager {
void showMySoftInput(in IBinder token, int flags);
void updateStatusIcon(in IBinder token, String packageName, int iconId);
void setImeWindowStatus(in IBinder token, int vis, int backDisposition);
+ void registerSuggestionSpansForNotification(in SuggestionSpan[] spans);
+ boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index);
InputMethodSubtype getCurrentInputMethodSubtype();
boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
boolean switchToLastInputMethod(in IBinder token);
boolean setInputMethodEnabled(String id, boolean enabled);
+ boolean setAdditionalInputMethodSubtypes(in IBinder token, in InputMethodSubtype[] subtypes);
}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index b13118a..a235d9a 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -251,7 +251,7 @@ public class InputConnectionWrapper implements InputConnection {
}
return value;
}
-
+
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
ExtractedText value = null;
try {
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
index 2d067da..b54daba 100644
--- a/core/java/com/android/internal/view/StandaloneActionMode.java
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -135,6 +135,6 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call
public void onMenuModeChange(MenuBuilder menu) {
invalidate();
- mContextView.openOverflowMenu();
+ mContextView.showOverflowMenu();
}
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index 0ef4861..a4bcf60 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -236,4 +236,31 @@ public class ActionMenuItem implements MenuItem {
public MenuItem setActionView(int resId) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ setShowAsAction(actionEnum);
+ return this;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean isActionViewExpanded() {
+ return false;
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ // No need to save the listener; ActionMenuItem does not support collapsing items.
+ return this;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 3325df6..479788d 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -18,6 +18,7 @@ package com.android.internal.view.menu;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
@@ -28,7 +29,7 @@ import android.widget.LinearLayout;
* @hide
*/
public class ActionMenuItemView extends LinearLayout
- implements MenuView.ItemView, View.OnClickListener {
+ implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
private static final String TAG = "ActionMenuItemView";
private MenuItemImpl mItemData;
@@ -56,6 +57,7 @@ public class ActionMenuItemView extends LinearLayout
mTextButton = (Button) findViewById(com.android.internal.R.id.textButton);
mImageButton.setOnClickListener(this);
mTextButton.setOnClickListener(this);
+ setOnClickListener(this);
}
public MenuItemImpl getItemData() {
@@ -102,6 +104,12 @@ public class ActionMenuItemView extends LinearLayout
// TODO Support checkable action items
}
+ private void updateTextButtonVisibility() {
+ boolean visible = !TextUtils.isEmpty(mTextButton.getText());
+ visible = visible && (mImageButton.getDrawable() == null || mItemData.showsTextAsAction());
+ mTextButton.setVisibility(visible ? VISIBLE : GONE);
+ }
+
public void setIcon(Drawable icon) {
mImageButton.setImageDrawable(icon);
if (icon != null) {
@@ -110,9 +118,9 @@ public class ActionMenuItemView extends LinearLayout
mImageButton.setVisibility(GONE);
}
- mTextButton.setVisibility(icon == null || mItemData.showsTextAsAction() ? VISIBLE : GONE);
+ updateTextButtonVisibility();
}
-
+
public boolean hasText() {
return mTextButton.getVisibility() != GONE;
}
@@ -127,13 +135,20 @@ public class ActionMenuItemView extends LinearLayout
// populate accessibility description with title
setContentDescription(title);
- if (mImageButton.getDrawable() == null || mItemData.showsTextAsAction()) {
- mTextButton.setText(mTitle);
- mTextButton.setVisibility(VISIBLE);
- }
+ mTextButton.setText(mTitle);
+
+ updateTextButtonVisibility();
}
public boolean showsIcon() {
return true;
}
+
+ public boolean needsDividerBefore() {
+ return hasText() && mItemData.getIcon() == null;
+ }
+
+ public boolean needsDividerAfter() {
+ return hasText();
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
new file mode 100644
index 0000000..98c2747
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.SparseBooleanArray;
+import android.view.MenuItem;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter {
+ private static final String TAG = "ActionMenuPresenter";
+
+ private View mOverflowButton;
+ private boolean mReserveOverflow;
+ private boolean mReserveOverflowSet;
+ private int mWidthLimit;
+ private int mActionItemWidthLimit;
+ private int mMaxItems;
+ private boolean mMaxItemsSet;
+ private boolean mStrictWidthLimit;
+ private boolean mWidthLimitSet;
+
+ // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+ private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+ private View mScrapActionButtonView;
+
+ private OverflowPopup mOverflowPopup;
+ private ActionButtonSubmenu mActionButtonPopup;
+
+ private OpenOverflowRunnable mPostedOpenRunnable;
+
+ public ActionMenuPresenter() {
+ super(com.android.internal.R.layout.action_menu_layout,
+ com.android.internal.R.layout.action_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ super.initForMenu(context, menu);
+
+ final Resources res = context.getResources();
+
+ if (!mReserveOverflowSet) {
+ // TODO Use the no-buttons specifier instead here
+ mReserveOverflow = res.getConfiguration()
+ .isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
+ }
+
+ if (!mWidthLimitSet) {
+ mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
+ }
+
+ // Measure for initial configuration
+ if (!mMaxItemsSet) {
+ mMaxItems = res.getInteger(com.android.internal.R.integer.max_action_buttons);
+ }
+
+ int width = mWidthLimit;
+ if (mReserveOverflow) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mContext);
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mOverflowButton.measure(spec, spec);
+ }
+ width -= mOverflowButton.getMeasuredWidth();
+ } else {
+ mOverflowButton = null;
+ }
+
+ mActionItemWidthLimit = width;
+
+ // Drop a scrap view as it may no longer reflect the proper context/config.
+ mScrapActionButtonView = null;
+ }
+
+ public void setWidthLimit(int width, boolean strict) {
+ mWidthLimit = width;
+ mStrictWidthLimit = strict;
+ mWidthLimitSet = true;
+ }
+
+ public void setReserveOverflow(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ mReserveOverflowSet = true;
+ }
+
+ public void setItemLimit(int itemCount) {
+ mMaxItems = itemCount;
+ mMaxItemsSet = true;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ MenuView result = super.getMenuView(root);
+ ((ActionMenuView) result).setPresenter(this);
+ return result;
+ }
+
+ @Override
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ View actionView = item.getActionView();
+ actionView = actionView != null && !item.hasCollapsibleActionView() ?
+ actionView : super.getItemView(item, convertView, parent);
+ actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
+ return actionView;
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+ itemView.initialize(item, 0);
+ ((ActionMenuItemView) itemView).setItemInvoker((ActionMenuView) mMenuView);
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return item.isActionButton();
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ super.updateMenuView(cleared);
+
+ if (mReserveOverflow && mMenu.getNonActionItems().size() > 0) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mContext);
+ mOverflowButton.setLayoutParams(
+ ((ActionMenuView) mMenuView).generateOverflowButtonLayoutParams());
+ }
+ ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+ if (parent != mMenuView) {
+ if (parent != null) {
+ parent.removeView(mOverflowButton);
+ }
+ ((ViewGroup) mMenuView).addView(mOverflowButton);
+ }
+ } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+ ((ViewGroup) mMenuView).removeView(mOverflowButton);
+ }
+ }
+
+ @Override
+ public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+ return super.filterLeftoverView(parent, childIndex);
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ SubMenuBuilder topSubMenu = subMenu;
+ while (topSubMenu.getParentMenu() != mMenu) {
+ topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+ }
+ View anchor = findViewForItem(topSubMenu.getItem());
+ if (anchor == null) return false;
+
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+ mActionButtonPopup.setAnchorView(anchor);
+ mActionButtonPopup.show();
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ private View findViewForItem(MenuItem item) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return null;
+
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof MenuView.ItemView &&
+ ((MenuView.ItemView) child).getItemData() == item) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Display the overflow menu if one is present.
+ * @return true if the overflow menu was shown, false otherwise.
+ */
+ public boolean showOverflowMenu() {
+ if (mReserveOverflow && !isOverflowMenuShowing() && mMenuView != null &&
+ mPostedOpenRunnable == null) {
+ OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+ mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+ // Post this for later; we might still need a layout for the anchor to be right.
+ ((View) mMenuView).post(mPostedOpenRunnable);
+
+ // ActionMenuPresenter uses null as a callback argument here
+ // to indicate overflow is opening.
+ super.onSubMenuSelected(null);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Hide the overflow menu if it is currently showing.
+ *
+ * @return true if the overflow menu was hidden, false otherwise.
+ */
+ public boolean hideOverflowMenu() {
+ if (mPostedOpenRunnable != null && mMenuView != null) {
+ ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+ return true;
+ }
+
+ MenuPopupHelper popup = mOverflowPopup;
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dismiss all popup menus - overflow and submenus.
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean dismissPopupMenus() {
+ boolean result = hideOverflowMenu();
+ result |= hideSubMenus();
+ return result;
+ }
+
+ /**
+ * Dismiss all submenu popups.
+ *
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean hideSubMenus() {
+ if (mActionButtonPopup != null) {
+ mActionButtonPopup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mOverflowPopup != null && mOverflowPopup.isShowing();
+ }
+
+ /**
+ * @return true if space has been reserved in the action menu for an overflow item.
+ */
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public boolean flagActionItems() {
+ final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ int maxActions = mMaxItems;
+ int widthLimit = mActionItemWidthLimit;
+ final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final ViewGroup parent = (ViewGroup) mMenuView;
+
+ int requiredItems = 0;
+ int requestedItems = 0;
+ int firstActionWidth = 0;
+ boolean hasOverflow = false;
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.requiresActionButton()) {
+ requiredItems++;
+ } else if (item.requestsActionButton()) {
+ requestedItems++;
+ } else {
+ hasOverflow = true;
+ }
+ }
+
+ // Reserve a spot for the overflow item if needed.
+ if (mReserveOverflow &&
+ (hasOverflow || requiredItems + requestedItems > maxActions)) {
+ maxActions--;
+ }
+ maxActions -= requiredItems;
+
+ final SparseBooleanArray seenGroups = mActionButtonGroups;
+ seenGroups.clear();
+
+ // Flag as many more requested items as will fit.
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+
+ if (item.requiresActionButton()) {
+ View v = item.getActionView();
+ if (v == null || item.hasCollapsibleActionView()) {
+ v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ }
+ v.measure(querySpec, querySpec);
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+ final int groupId = item.getGroupId();
+ if (groupId != 0) {
+ seenGroups.put(groupId, true);
+ }
+ } else if (item.requestsActionButton()) {
+ // Items in a group with other items that already have an action slot
+ // can break the max actions rule, but not the width limit.
+ final int groupId = item.getGroupId();
+ final boolean inGroup = seenGroups.get(groupId);
+ boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
+ maxActions--;
+
+ if (isAction) {
+ View v = item.getActionView();
+ if (v == null || item.hasCollapsibleActionView()) {
+ v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ }
+ v.measure(querySpec, querySpec);
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+
+ if (mStrictWidthLimit) {
+ isAction = widthLimit >= 0;
+ } else {
+ // Did this push the entire first item past the limit?
+ isAction = widthLimit + firstActionWidth > 0;
+ }
+ }
+
+ if (isAction && groupId != 0) {
+ seenGroups.put(groupId, true);
+ } else if (inGroup) {
+ // We broke the width limit. Demote the whole group, they all overflow now.
+ seenGroups.put(groupId, false);
+ for (int j = 0; j < i; j++) {
+ MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+ if (areYouMyGroupie.getGroupId() == groupId) {
+ areYouMyGroupie.setIsActionButton(false);
+ }
+ }
+ }
+
+ item.setIsActionButton(isAction);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ dismissPopupMenus();
+ super.onCloseMenu(menu, allMenusAreClosing);
+ }
+
+ private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
+ public OverflowMenuButton(Context context) {
+ super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
+
+ setClickable(true);
+ setFocusable(true);
+ setVisibility(VISIBLE);
+ setEnabled(true);
+ }
+
+ @Override
+ public boolean performClick() {
+ if (super.performClick()) {
+ return true;
+ }
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+ showOverflowMenu();
+ return true;
+ }
+
+ public boolean needsDividerBefore() {
+ return true;
+ }
+
+ public boolean needsDividerAfter() {
+ return false;
+ }
+ }
+
+ private class OverflowPopup extends MenuPopupHelper {
+ public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+ boolean overflowOnly) {
+ super(context, menu, anchorView, overflowOnly);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mMenu.close();
+ mOverflowPopup = null;
+ }
+ }
+
+ private class ActionButtonSubmenu extends MenuPopupHelper {
+ private SubMenuBuilder mSubMenu;
+
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+ super(context, subMenu);
+ mSubMenu = subMenu;
+
+ MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+ if (!item.isActionButton()) {
+ // Give a reasonable anchor to nested submenus.
+ setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+ }
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mSubMenu.close();
+ mActionButtonPopup = null;
+ }
+ }
+
+ private class OpenOverflowRunnable implements Runnable {
+ private OverflowPopup mPopup;
+
+ public OpenOverflowRunnable(OverflowPopup popup) {
+ mPopup = popup;
+ }
+
+ public void run() {
+ mMenu.changeMenuMode();
+ if (mPopup.tryShow()) {
+ mOverflowPopup = mPopup;
+ mPostedOpenRunnable = null;
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index 5da5e44..7b4f216 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -17,63 +17,25 @@ package com.android.internal.view.menu;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.SoundEffectConstants;
import android.view.View;
+import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import java.util.ArrayList;
-
/**
* @hide
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
-
- // TODO Theme/style this.
- private static final int DIVIDER_PADDING = 12; // dips
private MenuBuilder mMenu;
- private int mMaxItems;
- private int mWidthLimit;
private boolean mReserveOverflow;
- private OverflowMenuButton mOverflowButton;
- private MenuPopupHelper mOverflowPopup;
-
- private float mDividerPadding;
-
- private Drawable mDivider;
-
- private final Runnable mShowOverflow = new Runnable() {
- public void run() {
- showOverflowMenu();
- }
- };
-
- private class OpenOverflowRunnable implements Runnable {
- private MenuPopupHelper mPopup;
-
- public OpenOverflowRunnable(MenuPopupHelper popup) {
- mPopup = popup;
- }
-
- public void run() {
- if (mPopup.tryShow()) {
- mOverflowPopup = mPopup;
- mPostedOpenRunnable = null;
- }
- }
- }
-
- private OpenOverflowRunnable mPostedOpenRunnable;
+ private ActionMenuPresenter mPresenter;
+ private boolean mUpdateContentsBeforeMeasure;
+ private boolean mFormatItems;
public ActionMenuView(Context context) {
this(context, null);
@@ -81,57 +43,112 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
-
- final Resources res = getResources();
-
- // Measure for initial configuration
- mMaxItems = getMaxActionButtons();
-
- // TODO There has to be a better way to indicate that we don't have a hard menu key.
- final Configuration config = res.getConfiguration();
- mReserveOverflow = config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
- mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
-
- TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
- mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
- a.recycle();
-
- mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density;
-
setBaselineAligned(false);
}
+ public void setPresenter(ActionMenuPresenter presenter) {
+ mPresenter = presenter;
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- mReserveOverflow = newConfig.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
- mMaxItems = getMaxActionButtons();
- mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2;
- if (mMenu != null) {
- mMenu.setMaxActionItems(mMaxItems);
- updateChildren(false);
+ mPresenter.updateMenuView(false);
+
+ if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
}
+ }
- if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
- mOverflowPopup.dismiss();
- post(mShowOverflow);
+ @Override
+ public void requestLayout() {
+ // Layout can influence how many action items fit.
+ mUpdateContentsBeforeMeasure = true;
+ super.requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mUpdateContentsBeforeMeasure && mMenu != null) {
+ mMenu.onItemsChanged(true);
+ mUpdateContentsBeforeMeasure = false;
}
+ // If we've been given an exact size to match, apply special formatting during layout.
+ mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
- mOverflowPopup.dismiss();
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mFormatItems) {
+ super.onLayout(changed, left, top, right, bottom);
+ return;
+ }
+
+ final int childCount = getChildCount();
+ final int midVertical = (top + bottom) / 2;
+ final int dividerWidth = getDividerWidth();
+ int overflowWidth = 0;
+ int nonOverflowWidth = 0;
+ int nonOverflowCount = 0;
+ int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ if (v.getVisibility() == GONE) {
+ continue;
+ }
+
+ LayoutParams p = (LayoutParams) v.getLayoutParams();
+ if (p.isOverflowButton) {
+ overflowWidth = v.getMeasuredWidth();
+ if (hasDividerBeforeChildAt(i)) {
+ overflowWidth += dividerWidth;
+ }
+
+ int height = v.getMeasuredHeight();
+ int r = getPaddingRight();
+ int l = r - overflowWidth;
+ int t = midVertical - (height / 2);
+ int b = t + height;
+ v.layout(l, t, r, b);
+
+ widthRemaining -= overflowWidth;
+ } else {
+ nonOverflowWidth += v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
+ if (hasDividerBeforeChildAt(i)) {
+ nonOverflowWidth += dividerWidth;
+ }
+ nonOverflowCount++;
+ }
}
- removeCallbacks(mShowOverflow);
- if (mPostedOpenRunnable != null) {
- removeCallbacks(mPostedOpenRunnable);
+
+ // Fill action items from the left. Overflow will always pin to the right edge.
+ if (nonOverflowWidth <= widthRemaining - overflowWidth) {
+ widthRemaining -= overflowWidth;
+ }
+
+ int startLeft = getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
+
+ startLeft += lp.leftMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - (height / 2);
+ v.layout(startLeft, t, startLeft + width, t + height);
+ startLeft += width + lp.rightMargin;
}
}
- private int getMaxActionButtons() {
- return getResources().getInteger(com.android.internal.R.integer.max_action_buttons);
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPresenter.dismissPopupMenus();
}
public boolean isOverflowReserved() {
@@ -141,10 +158,6 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
-
- public View getOverflowButton() {
- return mOverflowButton;
- }
@Override
protected LayoutParams generateDefaultLayoutParams() {
@@ -166,6 +179,17 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return generateDefaultLayoutParams();
}
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
+ public LayoutParams generateOverflowButtonLayoutParams() {
+ LayoutParams result = generateDefaultLayoutParams();
+ result.isOverflowButton = true;
+ return result;
+ }
+
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
@@ -174,243 +198,50 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return 0;
}
- public void initialize(MenuBuilder menu, int menuType) {
- int width = mWidthLimit;
- if (mReserveOverflow) {
- if (mOverflowButton == null) {
- OverflowMenuButton button = new OverflowMenuButton(mContext);
- mOverflowButton = button;
- }
- final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- mOverflowButton.measure(spec, spec);
- width -= mOverflowButton.getMeasuredWidth();
- }
-
- menu.setActionWidthLimit(width);
-
- menu.setMaxActionItems(mMaxItems);
- final boolean cleared = mMenu != menu;
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
- updateChildren(cleared);
}
- public void updateChildren(boolean cleared) {
- final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(mReserveOverflow);
- final int itemCount = itemsToShow.size();
-
- boolean needsDivider = false;
- int childIndex = 0;
- for (int i = 0; i < itemCount; i++) {
- final MenuItemImpl itemData = itemsToShow.get(i);
- boolean hasDivider = false;
-
- if (needsDivider) {
- if (!isDivider(getChildAt(childIndex))) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- }
- hasDivider = true;
- childIndex++;
- }
-
- View childToAdd = itemData.getActionView();
- boolean needsPreDivider = false;
- if (childToAdd != null) {
- childToAdd.setLayoutParams(makeActionViewLayoutParams(childToAdd));
- } else {
- ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView(
- MenuBuilder.TYPE_ACTION_BUTTON, this);
- view.setItemInvoker(this);
- needsPreDivider = i > 0 && !hasDivider && view.hasText() &&
- itemData.getIcon() == null;
- needsDivider = view.hasText();
- childToAdd = view;
- }
-
- boolean addPreDivider = removeChildrenUntil(childIndex, childToAdd, needsPreDivider);
-
- if (addPreDivider) addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- if (needsPreDivider) childIndex++;
-
- if (getChildAt(childIndex) != childToAdd) {
- addView(childToAdd, childIndex);
- }
- childIndex++;
- }
-
- final boolean hasOverflow = mOverflowButton != null && mOverflowButton.getParent() == this;
- final boolean needsOverflow = mReserveOverflow && mMenu.getNonActionItems(true).size() > 0;
-
- if (hasOverflow != needsOverflow) {
- if (needsOverflow) {
- if (mOverflowButton == null) {
- OverflowMenuButton button = new OverflowMenuButton(mContext);
- mOverflowButton = button;
- }
- boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, true);
- if (addDivider && itemCount > 0) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- childIndex++;
- }
- addView(mOverflowButton, childIndex);
- childIndex++;
- } else {
- removeView(mOverflowButton);
- }
- } else {
- if (needsOverflow) {
- boolean overflowDivider = itemCount > 0;
- boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton,
- overflowDivider);
- if (addDivider && itemCount > 0) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- }
- if (overflowDivider) {
- childIndex += 2;
- } else {
- childIndex++;
- }
- }
- }
-
- while (getChildCount() > childIndex) {
- removeViewAt(childIndex);
- }
- }
-
- private boolean removeChildrenUntil(int start, View targetChild, boolean needsPreDivider) {
- final int childCount = getChildCount();
- boolean found = false;
- for (int i = start; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child == targetChild) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return needsPreDivider;
- }
-
- for (int i = start; i < getChildCount(); ) {
- final View child = getChildAt(i);
- if (needsPreDivider && isDivider(child)) {
- needsPreDivider = false;
- i++;
- continue;
- }
- if (child == targetChild) break;
- removeViewAt(i);
- }
-
- return needsPreDivider;
- }
-
- private static boolean isDivider(View v) {
- return v != null && v.getId() == com.android.internal.R.id.action_menu_divider;
- }
-
- public boolean showOverflowMenu() {
- if (mOverflowButton != null && !isOverflowMenuShowing()) {
- mMenu.getCallback().onMenuModeChange(mMenu);
- return true;
- }
- return false;
- }
-
- public void openOverflowMenu() {
- OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true);
- mPostedOpenRunnable = new OpenOverflowRunnable(popup);
- // Post this for later; we might still need a layout for the anchor to be right.
- post(mPostedOpenRunnable);
- }
-
- public boolean isOverflowMenuShowing() {
- return mOverflowPopup != null && mOverflowPopup.isShowing();
- }
-
- public boolean isOverflowMenuOpen() {
- return mOverflowPopup != null;
- }
-
- public boolean hideOverflowMenu() {
- if (mPostedOpenRunnable != null) {
- removeCallbacks(mPostedOpenRunnable);
- return true;
+ @Override
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ final View childBefore = getChildAt(childIndex - 1);
+ final View child = getChildAt(childIndex);
+ boolean result = false;
+ if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
}
-
- MenuPopupHelper popup = mOverflowPopup;
- if (popup != null) {
- popup.dismiss();
- return true;
+ if (childIndex > 0 && child instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) child).needsDividerBefore();
}
- return false;
- }
-
- private boolean addItemView(boolean needsDivider, ActionMenuItemView view) {
- view.setItemInvoker(this);
- boolean hasText = view.hasText();
-
- if (hasText && needsDivider) {
- addView(makeDividerView(), makeDividerLayoutParams());
- }
- addView(view);
- return hasText;
- }
-
- private ImageView makeDividerView() {
- ImageView result = new ImageView(mContext);
- result.setImageDrawable(mDivider);
- result.setScaleType(ImageView.ScaleType.FIT_XY);
- result.setId(com.android.internal.R.id.action_menu_divider);
return result;
}
- private LayoutParams makeDividerLayoutParams() {
- LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT);
- params.topMargin = (int) mDividerPadding;
- params.bottomMargin = (int) mDividerPadding;
- return params;
- }
-
- private LayoutParams makeActionViewLayoutParams(View view) {
- return generateLayoutParams(view.getLayoutParams());
+ public interface ActionMenuChildView {
+ public boolean needsDividerBefore();
+ public boolean needsDividerAfter();
}
- private class OverflowMenuButton extends ImageButton {
- public OverflowMenuButton(Context context) {
- super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
+ public static class LayoutParams extends LinearLayout.LayoutParams {
+ @ViewDebug.ExportedProperty(category = "layout")
+ public boolean isOverflowButton;
- setClickable(true);
- setFocusable(true);
- setVisibility(VISIBLE);
- setEnabled(true);
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
}
- @Override
- public boolean performClick() {
- if (super.performClick()) {
- return true;
- }
-
- playSoundEffect(SoundEffectConstants.CLICK);
- showOverflowMenu();
- return true;
+ public LayoutParams(LayoutParams other) {
+ super((LinearLayout.LayoutParams) other);
+ isOverflowButton = other.isOverflowButton;
}
- }
- private class OverflowPopup extends MenuPopupHelper {
- public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly) {
- super(context, menu, anchorView, overflowOnly);
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ isOverflowButton = false;
}
- @Override
- public void onDismiss() {
- super.onDismiss();
- mMenu.getCallback().onCloseMenu(mMenu, true);
- mOverflowPopup = null;
+ public LayoutParams(int width, int height, boolean isOverflowButton) {
+ super(width, height);
+ this.isOverflowButton = isOverflowButton;
}
}
}
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
new file mode 100644
index 0000000..ddbb08c
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Base class for MenuPresenters that have a consistent container view and item
+ * views. Behaves similarly to an AdapterView in that existing item views will
+ * be reused if possible when items change.
+ */
+public abstract class BaseMenuPresenter implements MenuPresenter {
+ protected Context mContext;
+ protected MenuBuilder mMenu;
+ protected LayoutInflater mInflater;
+ private Callback mCallback;
+
+ private int mMenuLayoutRes;
+ private int mItemLayoutRes;
+
+ protected MenuView mMenuView;
+
+ /**
+ * Construct a new BaseMenuPresenter.
+ *
+ * @param menuLayoutRes Layout resource ID for the menu container view
+ * @param itemLayoutRes Layout resource ID for a single item view
+ */
+ public BaseMenuPresenter(int menuLayoutRes, int itemLayoutRes) {
+ mMenuLayoutRes = menuLayoutRes;
+ mItemLayoutRes = itemLayoutRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = context;
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (MenuView) mInflater.inflate(mMenuLayoutRes, root, false);
+ mMenuView.initialize(mMenu);
+ updateMenuView(true);
+ }
+
+ return mMenuView;
+ }
+
+ /**
+ * Reuses item views when it can
+ */
+ public void updateMenuView(boolean cleared) {
+ mMenu.flagActionItems();
+ ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemCount = visibleItems.size();
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ int childIndex = 0;
+ for (int i = 0; i < itemCount; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (shouldIncludeItem(childIndex, item)) {
+ final View convertView = parent.getChildAt(childIndex);
+ final View itemView = getItemView(item, convertView, parent);
+ if (itemView != convertView) {
+ addItemView(itemView, childIndex);
+ }
+ childIndex++;
+ }
+ }
+
+ // Remove leftover views.
+ while (childIndex < parent.getChildCount()) {
+ if (!filterLeftoverView(parent, childIndex)) {
+ childIndex++;
+ }
+ }
+ }
+
+ /**
+ * Add an item view at the given index.
+ *
+ * @param itemView View to add
+ * @param childIndex Index within the parent to insert at
+ */
+ protected void addItemView(View itemView, int childIndex) {
+ final ViewGroup currentParent = (ViewGroup) itemView.getParent();
+ if (currentParent != null) {
+ currentParent.removeView(itemView);
+ }
+ ((ViewGroup) mMenuView).addView(itemView, childIndex);
+ }
+
+ /**
+ * Filter the child view at index and remove it if appropriate.
+ * @param parent Parent to filter from
+ * @param childIndex Index to filter
+ * @return true if the child view at index was removed
+ */
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ parent.removeViewAt(childIndex);
+ return true;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ /**
+ * Create a new item view that can be re-bound to other item data later.
+ *
+ * @return The new item view
+ */
+ public MenuView.ItemView createItemView(ViewGroup parent) {
+ return (MenuView.ItemView) mInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ /**
+ * Prepare an item view for use. See AdapterView for the basic idea at work here.
+ * This may require creating a new item view, but well-behaved implementations will
+ * re-use the view passed as convertView if present. The returned view will be populated
+ * with data from the item parameter.
+ *
+ * @param item Item to present
+ * @param convertView Existing view to reuse
+ * @param parent Intended parent view - use for inflation.
+ * @return View that presents the requested menu item
+ */
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ MenuView.ItemView itemView;
+ if (convertView instanceof MenuView.ItemView) {
+ itemView = (MenuView.ItemView) convertView;
+ } else {
+ itemView = createItemView(parent);
+ }
+ bindItemView(item, itemView);
+ return (View) itemView;
+ }
+
+ /**
+ * Bind item data to an existing item view.
+ *
+ * @param item Item to bind
+ * @param itemView View to populate with item data
+ */
+ public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
+
+ /**
+ * Filter item by child index and item data.
+ *
+ * @param childIndex Indended presentation index of this item
+ * @param item Item to present
+ * @return true if this item should be included in this menu presentation; false otherwise
+ */
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return true;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder menu) {
+ if (mCallback != null) {
+ return mCallback.onOpenSubMenu(menu);
+ }
+ return false;
+ }
+
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index 9e4b4ce..723ece4 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -17,17 +17,15 @@
package com.android.internal.view.menu;
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
-
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+import android.widget.ListView;
/**
* The expanded menu view is a list-like menu with all of the available menu items. It is opened
@@ -53,23 +51,8 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men
setOnItemClickListener(this);
}
- public void initialize(MenuBuilder menu, int menuType) {
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
-
- setAdapter(menu.new MenuAdapter(menuType));
- }
-
- public void updateChildren(boolean cleared) {
- ListAdapter adapter = getAdapter();
- // Tell adapter of the change, it will notify the mListView
- if (adapter != null) {
- if (cleared) {
- ((BaseAdapter)adapter).notifyDataSetInvalidated();
- }
- else {
- ((BaseAdapter)adapter).notifyDataSetChanged();
- }
- }
}
@Override
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 3c5b422..c337a5d 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -112,6 +112,10 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
setEnabled(itemData.isEnabled());
}
+ public void setItemData(MenuItemImpl data) {
+ mItemData = data;
+ }
+
@Override
public boolean performClick() {
// Let the view's click listener have top priority (the More button relies on this)
@@ -278,7 +282,7 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
getLineBounds(0, tmpRect);
mPositionIconAvailable.set(0, 0, getWidth(), tmpRect.top);
Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.LEFT, mIcon.getIntrinsicWidth(), mIcon
- .getIntrinsicHeight(), mPositionIconAvailable, mPositionIconOutput);
+ .getIntrinsicHeight(), mPositionIconAvailable, mPositionIconOutput, isLayoutRtl());
mIcon.setBounds(mPositionIconOutput);
}
diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
new file mode 100644
index 0000000..f717904
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.view.menu;
+
+import com.android.internal.view.menu.MenuView.ItemView;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for the classic "six-pack" icon menu.
+ */
+public class IconMenuPresenter extends BaseMenuPresenter {
+ private IconMenuItemView mMoreView;
+ private int mMaxItems = -1;
+
+ private static final String VIEWS_TAG = "android:menu:icon";
+
+ public IconMenuPresenter() {
+ super(com.android.internal.R.layout.icon_menu_layout,
+ com.android.internal.R.layout.icon_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu);
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ mMaxItems = -1;
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, ItemView itemView) {
+ final IconMenuItemView view = (IconMenuItemView) itemView;
+ view.setItemData(item);
+
+ view.initialize(item.getTitleForItemView(view), item.getIcon());
+
+ view.setVisibility(item.isVisible() ? View.VISIBLE : View.GONE);
+ view.setEnabled(view.isEnabled());
+ view.setLayoutParams(view.getTextAppropriateLayoutParams());
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+ boolean fits = (itemsToShow.size() == mMaxItems && childIndex < mMaxItems) ||
+ childIndex < mMaxItems - 1;
+ return fits && !item.isActionButton();
+ }
+
+ @Override
+ protected void addItemView(View itemView, int childIndex) {
+ final IconMenuItemView v = (IconMenuItemView) itemView;
+ final IconMenuView parent = (IconMenuView) mMenuView;
+
+ v.setIconMenuView(parent);
+ v.setItemInvoker(parent);
+ v.setBackgroundDrawable(parent.getItemBackgroundDrawable());
+ super.addItemView(itemView, childIndex);
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ // The window manager will give us a token.
+ new MenuDialogHelper(subMenu).show(null);
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ final IconMenuView menuView = (IconMenuView) mMenuView;
+ if (mMaxItems < 0) mMaxItems = menuView.getMaxItems();
+ final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+ final boolean needsMore = itemsToShow.size() > mMaxItems;
+ super.updateMenuView(cleared);
+
+ if (needsMore && (mMoreView == null || mMoreView.getParent() != menuView)) {
+ if (mMoreView == null) {
+ mMoreView = menuView.createMoreItemView();
+ mMoreView.setBackgroundDrawable(menuView.getItemBackgroundDrawable());
+ }
+ menuView.addView(mMoreView);
+ } else if (!needsMore && mMoreView != null) {
+ menuView.removeView(mMoreView);
+ }
+
+ menuView.setNumActualItemsShown(needsMore ? mMaxItems - 1 : itemsToShow.size());
+ }
+
+ @Override
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) != mMoreView) {
+ return super.filterLeftoverView(parent, childIndex);
+ }
+ return false;
+ }
+
+ public int getNumActualItemsShown() {
+ return ((IconMenuView) mMenuView).getNumActualItemsShown();
+ }
+
+ public void saveHierarchyState(Bundle outState) {
+ SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+ if (mMenuView != null) {
+ ((View) mMenuView).saveHierarchyState(viewStates);
+ }
+ outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+ }
+
+ public void restoreHierarchyState(Bundle inState) {
+ SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+ if (viewStates != null) {
+ ((View) mMenuView).restoreHierarchyState(viewStates);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index d18c9727..dab43eb 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -80,10 +80,7 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
/** Icon for the 'More' button */
private Drawable mMoreIcon;
-
- /** Item view for the 'More' button */
- private IconMenuItemView mMoreItemView;
-
+
/** Background of each item (should contain the selected and focused states) */
private Drawable mItemBackground;
@@ -172,6 +169,10 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
}
+ int getMaxItems() {
+ return mMaxItems;
+ }
+
/**
* Figures out the layout for the menu items.
*
@@ -277,23 +278,8 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
return true;
}
- /**
- * Adds an IconMenuItemView to this icon menu view.
- * @param itemView The item's view to add
- */
- private void addItemView(IconMenuItemView itemView) {
- // Set ourselves on the item view
- itemView.setIconMenuView(this);
-
- // Apply the background to the item view
- itemView.setBackgroundDrawable(
- mItemBackground.getConstantState().newDrawable(
- getContext().getResources()));
-
- // This class is the invoker for all its item views
- itemView.setItemInvoker(this);
-
- addView(itemView, itemView.getTextAppropriateLayoutParams());
+ Drawable getItemBackgroundDrawable() {
+ return mItemBackground.getConstantState().newDrawable(getContext().getResources());
}
/**
@@ -302,25 +288,23 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
* have a MenuItemData backing it.
* @return The IconMenuItemView for the 'More' button
*/
- private IconMenuItemView createMoreItemView() {
- LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
+ IconMenuItemView createMoreItemView() {
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
com.android.internal.R.layout.icon_menu_item_layout, null);
- Resources r = getContext().getResources();
+ Resources r = context.getResources();
itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
// Set up a click listener on the view since there will be no invocation sequence
// due to the lack of a MenuItemData this view
itemView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
- // Switches the menu to expanded mode
- MenuBuilder.Callback cb = mMenu.getCallback();
- if (cb != null) {
- // Call callback
- cb.onMenuModeChange(mMenu);
- }
+ // Switches the menu to expanded mode. Requires support from
+ // the menu's active callback.
+ mMenu.changeMenuMode();
}
});
@@ -328,51 +312,8 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
}
- public void initialize(MenuBuilder menu, int menuType) {
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
- updateChildren(true);
- }
-
- public void updateChildren(boolean cleared) {
- // This method does a clear refresh of children
- removeAllViews();
-
- // IconMenuView never wants content sorted for an overflow action button, since
- // it is never used in the presence of an overflow button.
- final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false);
- final int numItems = itemsToShow.size();
- final int numItemsThatCanFit = mMaxItems;
- // Minimum of the num that can fit and the num that we have
- final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
-
- MenuItemImpl itemData;
- // Traverse through all but the last item that can fit since that last item can either
- // be a 'More' button or a sixth item
- for (int i = 0; i < minFitMinus1AndNumItems; i++) {
- itemData = itemsToShow.get(i);
- addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
- }
-
- if (numItems > numItemsThatCanFit) {
- // If there are more items than we can fit, show the 'More' button to
- // switch to expanded mode
- if (mMoreItemView == null) {
- mMoreItemView = createMoreItemView();
- }
-
- addItemView(mMoreItemView);
-
- // The last view is the more button, so the actual number of items is one less than
- // the number that can fit
- mNumActualItemsShown = numItemsThatCanFit - 1;
- } else if (numItems == numItemsThatCanFit) {
- // There are exactly the number we can show, so show the last item
- final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
- addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
-
- // The items shown fit exactly
- mNumActualItemsShown = numItemsThatCanFit;
- }
}
/**
@@ -463,13 +404,6 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mHasStaleChildren) {
- mHasStaleChildren = false;
-
- // If we have stale data, resync with the menu
- updateChildren(false);
- }
-
int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
calculateItemFittingMetadata(measuredWidth);
layoutItems(measuredWidth);
@@ -564,6 +498,9 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
return mNumActualItemsShown;
}
+ void setNumActualItemsShown(int count) {
+ mNumActualItemsShown = count;
+ }
public int getWindowAnimations() {
return mAnimations;
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 02584b6..0c3c605 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -48,6 +48,8 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
private int mMenuType;
+ private LayoutInflater mInflater;
+
public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
@@ -187,7 +189,7 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
public void setIcon(Drawable icon) {
- final boolean showIcon = mItemData.shouldShowIcon(mMenuType);
+ final boolean showIcon = mItemData.shouldShowIcon();
if (!showIcon && !mPreserveIconSpacing) {
return;
}
@@ -212,14 +214,14 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
private void insertIconView() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
this, false);
addView(mIconView, 0);
}
private void insertRadioButton() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mRadioButton =
(RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
this, false);
@@ -227,7 +229,7 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
private void insertCheckBox() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mCheckBox =
(CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
this, false);
@@ -242,4 +244,10 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
return false;
}
+ private LayoutInflater getInflater() {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(mContext);
+ }
+ return mInflater;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
new file mode 100644
index 0000000..f8d24a3
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for list-style menus.
+ */
+public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
+ Context mContext;
+ LayoutInflater mInflater;
+ MenuBuilder mMenu;
+
+ ExpandedMenuView mMenuView;
+
+ private int mItemIndexOffset;
+ int mThemeRes;
+ int mItemLayoutRes;
+
+ private Callback mCallback;
+ private MenuAdapter mAdapter;
+
+ public static final String VIEWS_TAG = "android:menu:list";
+
+ /**
+ * Construct a new ListMenuPresenter.
+ * @param context Context to use for theming. This will supersede the context provided
+ * to initForMenu when this presenter is added.
+ * @param itemLayoutRes Layout resource for individual item views.
+ */
+ public ListMenuPresenter(Context context, int itemLayoutRes) {
+ this(itemLayoutRes, 0);
+ mContext = context;
+ }
+
+ /**
+ * Construct a new ListMenuPresenter.
+ * @param itemLayoutRes Layout resource for individual item views.
+ * @param themeRes Resource ID of a theme to use for views.
+ */
+ public ListMenuPresenter(int itemLayoutRes, int themeRes) {
+ mItemLayoutRes = itemLayoutRes;
+ mThemeRes = themeRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ if (mThemeRes != 0) {
+ mContext = new ContextThemeWrapper(context, mThemeRes);
+ } else if (mContext == null) {
+ mContext = context;
+ }
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (ExpandedMenuView) mInflater.inflate(
+ com.android.internal.R.layout.expanded_menu_layout, root, false);
+ if (mAdapter == null) {
+ mAdapter = new MenuAdapter();
+ }
+ mMenuView.setAdapter(mAdapter);
+ mMenuView.setOnItemClickListener(this);
+ }
+ return mMenuView;
+ }
+
+ /**
+ * Call this instead of getMenuView if you want to manage your own ListView.
+ * For proper operation, the ListView hosting this adapter should add
+ * this presenter as an OnItemClickListener.
+ *
+ * @return A ListAdapter containing the items in the menu.
+ */
+ public ListAdapter getAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new MenuAdapter();
+ }
+ return mAdapter;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mAdapter != null) mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ // The window manager will give us a token.
+ new MenuDialogHelper(subMenu).show(null);
+ if (mCallback != null) {
+ mCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ int getItemIndexOffset() {
+ return mItemIndexOffset;
+ }
+
+ public void setItemIndexOffset(int offset) {
+ mItemIndexOffset = offset;
+ if (mMenuView != null) {
+ updateMenuView(false);
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mMenu.performItemAction(mAdapter.getItem(position), 0);
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public void saveHierarchyState(Bundle outState) {
+ SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+ if (mMenuView != null) {
+ ((View) mMenuView).saveHierarchyState(viewStates);
+ }
+ outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+ }
+
+ public void restoreHierarchyState(Bundle inState) {
+ SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+ ((View) mMenuView).restoreHierarchyState(viewStates);
+ }
+
+ private class MenuAdapter extends BaseAdapter {
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ return items.size() - mItemIndexOffset;
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ return items.get(position + mItemIndexOffset);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 14d0ac5..fdfa954 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -25,29 +25,22 @@ import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
import android.os.Parcelable;
+import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TypedValue;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.ContextThemeWrapper;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
-import java.util.Vector;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Implementation of the {@link android.view.Menu} interface for creating a
@@ -55,60 +48,6 @@ import java.util.Vector;
*/
public class MenuBuilder implements Menu {
private static final String LOGTAG = "MenuBuilder";
-
- /** The number of different menu types */
- public static final int NUM_TYPES = 5;
- /** The menu type that represents the icon menu view */
- public static final int TYPE_ICON = 0;
- /** The menu type that represents the expanded menu view */
- public static final int TYPE_EXPANDED = 1;
- /**
- * The menu type that represents a menu dialog. Examples are context and sub
- * menus. This menu type will not have a corresponding MenuView, but it will
- * have an ItemView.
- */
- public static final int TYPE_DIALOG = 2;
- /**
- * The menu type that represents a button in the application's action bar.
- */
- public static final int TYPE_ACTION_BUTTON = 3;
- /**
- * The menu type that represents a menu popup.
- */
- public static final int TYPE_POPUP = 4;
-
- private static final String VIEWS_TAG = "android:views";
-
- private static final int THEME_SYSTEM_DEFAULT = 0;
- private static final int THEME_APPLICATION = -1;
- private static final int THEME_ALERT_DIALOG = -2;
-
- // Order must be the same order as the TYPE_*
- static final int THEME_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.style.Theme_IconMenu,
- com.android.internal.R.style.Theme_ExpandedMenu,
- THEME_ALERT_DIALOG,
- THEME_APPLICATION,
- THEME_APPLICATION,
- };
-
- // Order must be the same order as the TYPE_*
- static final int LAYOUT_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.layout.icon_menu_layout,
- com.android.internal.R.layout.expanded_menu_layout,
- 0,
- com.android.internal.R.layout.action_menu_layout,
- 0,
- };
-
- // Order must be the same order as the TYPE_*
- static final int ITEM_LAYOUT_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.layout.icon_menu_item_layout,
- com.android.internal.R.layout.list_menu_item_layout,
- com.android.internal.R.layout.list_menu_item_layout,
- com.android.internal.R.layout.action_menu_item_layout,
- com.android.internal.R.layout.popup_menu_item_layout,
- };
private static final int[] sCategoryToOrder = new int[] {
1, /* No category */
@@ -160,14 +99,7 @@ public class MenuBuilder implements Menu {
* Contains items that should NOT appear in the Action Bar, if present.
*/
private ArrayList<MenuItemImpl> mNonActionItems;
- /**
- * The number of visible action buttons permitted in this menu
- */
- private int mMaxActionItems;
- /**
- * The total width limit in pixels for all action items within a menu
- */
- private int mActionWidthLimit;
+
/**
* Whether or not the items (or any one item's action state) has changed since it was
* last fetched.
@@ -175,12 +107,6 @@ public class MenuBuilder implements Menu {
private boolean mIsActionItemsStale;
/**
- * Whether the process of granting space as action items should reserve a space for
- * an overflow option in the action list.
- */
- private boolean mReserveActionOverflow;
-
- /**
* Default value for how added items should show in the action list.
*/
private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
@@ -210,100 +136,19 @@ public class MenuBuilder implements Menu {
* that may individually call onItemsChanged.
*/
private boolean mPreventDispatchingItemsChanged = false;
+ private boolean mItemsChangedWhileDispatchPrevented = false;
private boolean mOptionalIconsVisible = false;
- private ViewGroup mMeasureActionButtonParent;
-
- private final WeakReference<MenuAdapter>[] mAdapterCache =
- new WeakReference[NUM_TYPES];
- private final WeakReference<OverflowMenuAdapter>[] mOverflowAdapterCache =
- new WeakReference[NUM_TYPES];
-
- // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
- private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
-
- private static int getAlertDialogTheme(Context context) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
- outValue, true);
- return outValue.resourceId;
- }
-
- private MenuType[] mMenuTypes;
- class MenuType {
- private int mMenuType;
-
- /** The layout inflater that uses the menu type's theme */
- private LayoutInflater mInflater;
-
- /** The lazily loaded {@link MenuView} */
- private WeakReference<MenuView> mMenuView;
+ private boolean mIsClosing = false;
- MenuType(int menuType) {
- mMenuType = menuType;
- }
-
- LayoutInflater getInflater() {
- // Create an inflater that uses the given theme for the Views it inflates
- if (mInflater == null) {
- Context wrappedContext;
- int themeResForType = THEME_RES_FOR_TYPE[mMenuType];
- switch (themeResForType) {
- case THEME_APPLICATION:
- wrappedContext = mContext;
- break;
- case THEME_ALERT_DIALOG:
- wrappedContext = new ContextThemeWrapper(mContext,
- getAlertDialogTheme(mContext));
- break;
- default:
- wrappedContext = new ContextThemeWrapper(mContext, themeResForType);
- break;
- }
- mInflater = (LayoutInflater) wrappedContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- return mInflater;
- }
-
- MenuView getMenuView(ViewGroup parent) {
- if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
- return null;
- }
+ private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
- synchronized (this) {
- MenuView menuView = mMenuView != null ? mMenuView.get() : null;
-
- if (menuView == null) {
- menuView = (MenuView) getInflater().inflate(
- LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
- menuView.initialize(MenuBuilder.this, mMenuType);
-
- // Cache the view
- mMenuView = new WeakReference<MenuView>(menuView);
-
- if (mFrozenViewStates != null) {
- View view = (View) menuView;
- view.restoreHierarchyState(mFrozenViewStates);
-
- // Clear this menu type's frozen state, since we just restored it
- mFrozenViewStates.remove(view.getId());
- }
- }
-
- return menuView;
- }
- }
-
- boolean hasMenuView() {
- return mMenuView != null && mMenuView.get() != null;
- }
- }
+ private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
+ new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
/**
- * Called by menu to notify of close and selection changes
+ * Called by menu to notify of close and selection changes.
*/
public interface Callback {
/**
@@ -315,30 +160,6 @@ public class MenuBuilder implements Menu {
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
/**
- * Called when a menu is closed.
- * @param menu The menu that was closed.
- * @param allMenusAreClosing Whether the menus are completely closing (true),
- * or whether there is another menu opening shortly
- * (false). For example, if the menu is closing because a
- * sub menu is about to be shown, <var>allMenusAreClosing</var>
- * is false.
- */
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
-
- /**
- * Called when a sub menu is selected. This is a cue to open the given sub menu's decor.
- * @param subMenu the sub menu that is being opened
- * @return whether the sub menu selection was handled by the callback
- */
- public boolean onSubMenuSelected(SubMenuBuilder subMenu);
-
- /**
- * Called when a sub menu is closed
- * @param menu the sub menu that was closed
- */
- public void onCloseSubMenu(SubMenuBuilder menu);
-
- /**
* Called when the mode of the menu changes (for example, from icon to expanded).
*
* @param menu the menu that has changed modes
@@ -354,8 +175,6 @@ public class MenuBuilder implements Menu {
}
public MenuBuilder(Context context) {
- mMenuTypes = new MenuType[NUM_TYPES];
-
mContext = context;
mResources = context.getResources();
@@ -375,82 +194,68 @@ public class MenuBuilder implements Menu {
mDefaultShowAsAction = defaultShowAsAction;
return this;
}
-
- public void setCallback(Callback callback) {
- mCallback = callback;
- }
- MenuType getMenuType(int menuType) {
- if (mMenuTypes[menuType] == null) {
- mMenuTypes[menuType] = new MenuType(menuType);
- }
-
- return mMenuTypes[menuType];
+ /**
+ * Add a presenter to this menu. This will only hold a WeakReference;
+ * you do not need to explicitly remove a presenter, but you can using
+ * {@link #removeMenuPresenter(MenuPresenter)}.
+ *
+ * @param presenter The presenter to add
+ */
+ public void addMenuPresenter(MenuPresenter presenter) {
+ mPresenters.add(new WeakReference<MenuPresenter>(presenter));
+ presenter.initForMenu(mContext, this);
+ mIsActionItemsStale = true;
}
-
+
/**
- * Gets a menu View that contains this menu's items.
- *
- * @param menuType The type of menu to get a View for (must be one of
- * {@link #TYPE_ICON}, {@link #TYPE_EXPANDED},
- * {@link #TYPE_DIALOG}).
- * @param parent The ViewGroup that provides a set of LayoutParams values
- * for this menu view
- * @return A View for the menu of type <var>menuType</var>
+ * Remove a presenter from this menu. That presenter will no longer
+ * receive notifications of updates to this menu's data.
+ *
+ * @param presenter The presenter to remove
*/
- public View getMenuView(int menuType, ViewGroup parent) {
- // The expanded menu depends on the number if items shown in the icon menu (which
- // is adjustable as setters/XML attributes on IconMenuView [imagine a larger LCD
- // wanting to show more icons]). If, for example, the activity goes through
- // an orientation change while the expanded menu is open, the icon menu's view
- // won't have an instance anymore; so here we make sure we have an icon menu view (matching
- // the same parent so the layout parameters from the XML are used). This
- // will create the icon menu view and cache it (if it doesn't already exist).
- if (menuType == TYPE_EXPANDED
- && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
- getMenuType(TYPE_ICON).getMenuView(parent);
+ public void removeMenuPresenter(MenuPresenter presenter) {
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter item = ref.get();
+ if (item == null || item == presenter) {
+ mPresenters.remove(ref);
+ }
}
-
- return (View) getMenuType(menuType).getMenuView(parent);
}
- private int getNumIconMenuItemsShown() {
- ViewGroup parent = null;
-
- if (!mMenuTypes[TYPE_ICON].hasMenuView()) {
- /*
- * There isn't an icon menu view instantiated, so when we get it
- * below, it will lazily instantiate it. We should pass a proper
- * parent so it uses the layout_ attributes present in the XML
- * layout file.
- */
- if (mMenuTypes[TYPE_EXPANDED].hasMenuView()) {
- View expandedMenuView = (View) mMenuTypes[TYPE_EXPANDED].getMenuView(null);
- parent = (ViewGroup) expandedMenuView.getParent();
+ private void dispatchPresenterUpdate(boolean cleared) {
+ if (mPresenters.isEmpty()) return;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.updateMenuView(cleared);
}
}
-
- return ((IconMenuView) getMenuView(TYPE_ICON, parent)).getNumActualItemsShown();
+ startDispatchingItemsChanged();
}
- /**
- * Clears the cached menu views. Call this if the menu views need to another
- * layout (for example, if the screen size has changed).
- */
- public void clearMenuViews() {
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (mMenuTypes[i] != null) {
- mMenuTypes[i].mMenuView = null;
- }
- }
-
- for (int i = mItems.size() - 1; i >= 0; i--) {
- MenuItemImpl item = mItems.get(i);
- if (item.hasSubMenu()) {
- ((SubMenuBuilder) item.getSubMenu()).clearMenuViews();
+ private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean result = false;
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if (!result) {
+ result = presenter.onSubMenuSelected(subMenu);
}
- item.clearItemViews();
}
+ return result;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
}
/**
@@ -468,7 +273,7 @@ public class MenuBuilder implements Menu {
}
mItems.add(findInsertIndex(mItems, ordering), item);
- onItemsChanged(false);
+ onItemsChanged(true);
return item;
}
@@ -554,7 +359,7 @@ public class MenuBuilder implements Menu {
}
// Notify menu views
- onItemsChanged(false);
+ onItemsChanged(true);
}
}
@@ -573,7 +378,7 @@ public class MenuBuilder implements Menu {
mItems.remove(index);
- if (updateChildrenOnMenuViews) onItemsChanged(false);
+ if (updateChildrenOnMenuViews) onItemsChanged(true);
}
public void removeItemAt(int index) {
@@ -585,6 +390,7 @@ public class MenuBuilder implements Menu {
clear();
clearHeader();
mPreventDispatchingItemsChanged = false;
+ mItemsChangedWhileDispatchPrevented = false;
onItemsChanged(true);
}
@@ -725,19 +531,14 @@ public class MenuBuilder implements Menu {
return mItems.get(index);
}
- public MenuItem getOverflowItem(int index) {
- flagActionItems(true);
- return mNonActionItems.get(index);
- }
-
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return findItemWithShortcutForKey(keyCode, event) != null;
}
public void setQwertyMode(boolean isQwerty) {
mQwertyMode = isQwerty;
-
- refreshShortcuts(isShortcutsVisible(), isQwerty);
+
+ onItemsChanged(false);
}
/**
@@ -751,8 +552,7 @@ public class MenuBuilder implements Menu {
* @return An ordering integer that can be used to order this item across
* all the items (even from other categories).
*/
- private static int getOrdering(int categoryOrder)
- {
+ private static int getOrdering(int categoryOrder) {
final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
if (index < 0 || index >= sCategoryToOrder.length) {
@@ -770,23 +570,6 @@ public class MenuBuilder implements Menu {
}
/**
- * Refreshes the shortcut labels on each of the displayed items. Passes the arguments
- * so submenus don't need to call their parent menu for the same values.
- */
- private void refreshShortcuts(boolean shortcutsVisible, boolean qwertyMode) {
- MenuItemImpl item;
- for (int i = mItems.size() - 1; i >= 0; i--) {
- item = mItems.get(i);
-
- if (item.hasSubMenu()) {
- ((MenuBuilder) item.getSubMenu()).refreshShortcuts(shortcutsVisible, qwertyMode);
- }
-
- item.refreshShortcutOnItemViews(shortcutsVisible, qwertyMode);
- }
- }
-
- /**
* Sets whether the shortcuts should be visible on menus. Devices without hardware
* key input will never make shortcuts visible even if this method is passed 'true'.
*
@@ -798,7 +581,7 @@ public class MenuBuilder implements Menu {
if (mShortcutsVisible == shortcutsVisible) return;
setShortcutsVisibleInner(shortcutsVisible);
- refreshShortcuts(mShortcutsVisible, isQwertyMode());
+ onItemsChanged(false);
}
private void setShortcutsVisibleInner(boolean shortcutsVisible) {
@@ -818,15 +601,24 @@ public class MenuBuilder implements Menu {
Resources getResources() {
return mResources;
}
-
- public Callback getCallback() {
- return mCallback;
- }
public Context getContext() {
return mContext;
}
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mCallback != null && mCallback.onMenuItemSelected(menu, item);
+ }
+
+ /**
+ * Dispatch a mode change event to this menu's callback.
+ */
+ public void changeMenuMode() {
+ if (mCallback != null) {
+ mCallback.onMenuModeChange(this);
+ }
+ }
+
private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
for (int i = items.size() - 1; i >= 0; i--) {
MenuItemImpl item = items.get(i);
@@ -860,7 +652,7 @@ public class MenuBuilder implements Menu {
* (the ALT-enabled char corresponds to the shortcut) associated
* with the keyCode.
*/
- List<MenuItemImpl> findItemsWithShortcutForKey(int keyCode, KeyEvent event) {
+ void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
final boolean qwerty = isQwertyMode();
final int metaState = event.getMetaState();
final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
@@ -868,18 +660,15 @@ public class MenuBuilder implements Menu {
final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
// The delete key is not mapped to '\b' so we treat it specially
if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
- return null;
+ return;
}
- Vector<MenuItemImpl> items = new Vector();
// Look for an item whose shortcut is this key.
final int N = mItems.size();
for (int i = 0; i < N; i++) {
MenuItemImpl item = mItems.get(i);
if (item.hasSubMenu()) {
- List<MenuItemImpl> subMenuItems = ((MenuBuilder)item.getSubMenu())
- .findItemsWithShortcutForKey(keyCode, event);
- items.addAll(subMenuItems);
+ ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
}
final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
@@ -892,7 +681,6 @@ public class MenuBuilder implements Menu {
items.add(item);
}
}
- return items;
}
/*
@@ -908,9 +696,11 @@ public class MenuBuilder implements Menu {
*/
MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
// Get all items that can be associated directly or indirectly with the keyCode
- List<MenuItemImpl> items = findItemsWithShortcutForKey(keyCode, event);
+ ArrayList<MenuItemImpl> items = mTempShortcutItemList;
+ items.clear();
+ findItemsWithShortcutForKey(items, keyCode, event);
- if (items == null) {
+ if (items.isEmpty()) {
return null;
}
@@ -920,15 +710,18 @@ public class MenuBuilder implements Menu {
event.getKeyData(possibleChars);
// If we have only one element, we can safely returns it
- if (items.size() == 1) {
+ final int size = items.size();
+ if (size == 1) {
return items.get(0);
}
final boolean qwerty = isQwertyMode();
// If we found more than one item associated with the key,
// we have to return the exact match
- for (MenuItemImpl item : items) {
- final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
+ for (int i = 0; i < size; i++) {
+ final MenuItemImpl item = items.get(i);
+ final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
if ((shortcutChar == possibleChars.meta[0] &&
(metaState & KeyEvent.META_ALT_ON) == 0)
|| (shortcutChar == possibleChars.meta[2] &&
@@ -951,18 +744,18 @@ public class MenuBuilder implements Menu {
if (itemImpl == null || !itemImpl.isEnabled()) {
return false;
- }
+ }
boolean invoked = itemImpl.invoke();
- if (item.hasSubMenu()) {
+ if (itemImpl.hasCollapsibleActionView()) {
+ invoked |= itemImpl.expandActionView();
+ if (invoked) close(true);
+ } else if (item.hasSubMenu()) {
close(false);
- if (mCallback != null) {
- // Return true if the sub menu was invoked or the item was invoked previously
- invoked = mCallback.onSubMenuSelected((SubMenuBuilder) item.getSubMenu())
- || invoked;
- }
+ invoked |= dispatchSubMenuSelected((SubMenuBuilder) item.getSubMenu());
+ if (!invoked) close(true);
} else {
if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
close(true);
@@ -982,10 +775,18 @@ public class MenuBuilder implements Menu {
* is false.
*/
final void close(boolean allMenusAreClosing) {
- Callback callback = getCallback();
- if (callback != null) {
- callback.onCloseMenu(this, allMenusAreClosing);
+ if (mIsClosing) return;
+
+ mIsClosing = true;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.onCloseMenu(this, allMenusAreClosing);
+ }
}
+ mIsClosing = false;
}
/** {@inheritDoc} */
@@ -996,26 +797,40 @@ public class MenuBuilder implements Menu {
/**
* Called when an item is added or removed.
*
- * @param cleared Whether the items were cleared or just changed.
+ * @param structureChanged true if the menu structure changed,
+ * false if only item properties changed.
*/
- private void onItemsChanged(boolean cleared) {
+ void onItemsChanged(boolean structureChanged) {
if (!mPreventDispatchingItemsChanged) {
- if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
- if (mIsActionItemsStale == false) mIsActionItemsStale = true;
-
- MenuType[] menuTypes = mMenuTypes;
- for (int i = 0; i < NUM_TYPES; i++) {
- if ((menuTypes[i] != null) && (menuTypes[i].hasMenuView())) {
- MenuView menuView = menuTypes[i].mMenuView.get();
- menuView.updateChildren(cleared);
- }
+ if (structureChanged) {
+ mIsVisibleItemsStale = true;
+ mIsActionItemsStale = true;
+ }
- MenuAdapter adapter = mAdapterCache[i] == null ? null : mAdapterCache[i].get();
- if (adapter != null) adapter.notifyDataSetChanged();
+ dispatchPresenterUpdate(structureChanged);
+ } else {
+ mItemsChangedWhileDispatchPrevented = true;
+ }
+ }
- adapter = mOverflowAdapterCache[i] == null ? null : mOverflowAdapterCache[i].get();
- if (adapter != null) adapter.notifyDataSetChanged();
- }
+ /**
+ * Stop dispatching item changed events to presenters until
+ * {@link #startDispatchingItemsChanged()} is called. Useful when
+ * many menu operations are going to be performed as a batch.
+ */
+ public void stopDispatchingItemsChanged() {
+ if (!mPreventDispatchingItemsChanged) {
+ mPreventDispatchingItemsChanged = true;
+ mItemsChangedWhileDispatchPrevented = false;
+ }
+ }
+
+ public void startDispatchingItemsChanged() {
+ mPreventDispatchingItemsChanged = false;
+
+ if (mItemsChangedWhileDispatchPrevented) {
+ mItemsChangedWhileDispatchPrevented = false;
+ onItemsChanged(true);
}
}
@@ -1025,6 +840,7 @@ public class MenuBuilder implements Menu {
*/
void onItemVisibleChanged(MenuItemImpl item) {
// Notify of items being changed
+ mIsVisibleItemsStale = true;
onItemsChanged(false);
}
@@ -1034,6 +850,7 @@ public class MenuBuilder implements Menu {
*/
void onItemActionRequestChanged(MenuItemImpl item) {
// Notify of items being changed
+ mIsActionItemsStale = true;
onItemsChanged(false);
}
@@ -1055,17 +872,6 @@ public class MenuBuilder implements Menu {
return mVisibleItems;
}
-
- /**
- * @return A fake action button parent view for obtaining child views.
- */
- private ViewGroup getMeasureActionButtonParent() {
- if (mMeasureActionButtonParent == null) {
- mMeasureActionButtonParent = (ViewGroup) getMenuType(TYPE_ACTION_BUTTON).getInflater()
- .inflate(LAYOUT_RES_FOR_TYPE[TYPE_ACTION_BUTTON], null, false);
- }
- return mMeasureActionButtonParent;
- }
/**
* This method determines which menu items get to be 'action items' that will appear
@@ -1090,147 +896,56 @@ public class MenuBuilder implements Menu {
* <p>The space freed by demoting a full group cannot be consumed by future menu items.
* Once items begin to overflow, all future items become overflow items as well. This is
* to avoid inadvertent reordering that may break the app's intended design.
- *
- * @param reserveActionOverflow true if an overflow button should consume one space
- * in the available item count
*/
- private void flagActionItems(boolean reserveActionOverflow) {
- if (reserveActionOverflow != mReserveActionOverflow) {
- mReserveActionOverflow = reserveActionOverflow;
- mIsActionItemsStale = true;
- }
-
+ public void flagActionItems() {
if (!mIsActionItemsStale) {
return;
}
- final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
- final int itemsSize = visibleItems.size();
- int maxActions = mMaxActionItems;
- int widthLimit = mActionWidthLimit;
- final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final ViewGroup parent = getMeasureActionButtonParent();
-
- int requiredItems = 0;
- int requestedItems = 0;
- int firstActionWidth = 0;
- boolean hasOverflow = false;
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
- if (item.requiresActionButton()) {
- requiredItems++;
- } else if (item.requestsActionButton()) {
- requestedItems++;
+ // Presenters flag action items as needed.
+ boolean flagged = false;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
} else {
- hasOverflow = true;
+ flagged |= presenter.flagActionItems();
}
}
- // Reserve a spot for the overflow item if needed.
- if (reserveActionOverflow &&
- (hasOverflow || requiredItems + requestedItems > maxActions)) {
- maxActions--;
- }
- maxActions -= requiredItems;
-
- final SparseBooleanArray seenGroups = mActionButtonGroups;
- seenGroups.clear();
-
- // Flag as many more requested items as will fit.
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
-
- if (item.requiresActionButton()) {
- View v = item.getActionView();
- if (v == null) {
- v = item.getItemView(TYPE_ACTION_BUTTON, parent);
- }
- v.measure(querySpec, querySpec);
- final int measuredWidth = v.getMeasuredWidth();
- widthLimit -= measuredWidth;
- if (firstActionWidth == 0) {
- firstActionWidth = measuredWidth;
+ if (flagged) {
+ mActionItems.clear();
+ mNonActionItems.clear();
+ ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.isActionButton()) {
+ mActionItems.add(item);
+ } else {
+ mNonActionItems.add(item);
}
- final int groupId = item.getGroupId();
- if (groupId != 0) {
- seenGroups.put(groupId, true);
- }
- } else if (item.requestsActionButton()) {
- // Items in a group with other items that already have an action slot
- // can break the max actions rule, but not the width limit.
- final int groupId = item.getGroupId();
- final boolean inGroup = seenGroups.get(groupId);
- boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
- maxActions--;
-
- if (isAction) {
- View v = item.getActionView();
- if (v == null) {
- v = item.getItemView(TYPE_ACTION_BUTTON, parent);
- }
- v.measure(querySpec, querySpec);
- final int measuredWidth = v.getMeasuredWidth();
- widthLimit -= measuredWidth;
- if (firstActionWidth == 0) {
- firstActionWidth = measuredWidth;
- }
-
- // Did this push the entire first item past halfway?
- if (widthLimit + firstActionWidth <= 0) {
- isAction = false;
- }
- }
-
- if (isAction && groupId != 0) {
- seenGroups.put(groupId, true);
- } else if (inGroup) {
- // We broke the width limit. Demote the whole group, they all overflow now.
- seenGroups.put(groupId, false);
- for (int j = 0; j < i; j++) {
- MenuItemImpl areYouMyGroupie = visibleItems.get(j);
- if (areYouMyGroupie.getGroupId() == groupId) {
- areYouMyGroupie.setIsActionButton(false);
- }
- }
- }
-
- item.setIsActionButton(isAction);
}
+ } else if (mActionItems.size() + mNonActionItems.size() != getVisibleItems().size()) {
+ // Nobody flagged anything, but if something doesn't add up then treat everything
+ // as non-action items.
+ // (This happens during a first pass with no action-item presenters.)
+ mActionItems.clear();
+ mNonActionItems.clear();
+ mNonActionItems.addAll(getVisibleItems());
}
-
- mActionItems.clear();
- mNonActionItems.clear();
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
- if (item.isActionButton()) {
- mActionItems.add(item);
- } else {
- mNonActionItems.add(item);
- }
- }
-
mIsActionItemsStale = false;
}
- ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) {
- flagActionItems(reserveActionOverflow);
+ ArrayList<MenuItemImpl> getActionItems() {
+ flagActionItems();
return mActionItems;
}
- ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) {
- flagActionItems(reserveActionOverflow);
+ ArrayList<MenuItemImpl> getNonActionItems() {
+ flagActionItems();
return mNonActionItems;
}
-
- void setMaxActionItems(int maxActionItems) {
- mMaxActionItems = maxActionItems;
- mIsActionItemsStale = true;
- }
-
- void setActionWidthLimit(int widthLimit) {
- mActionWidthLimit = widthLimit;
- mIsActionItemsStale = true;
- }
public void clearHeader() {
mHeaderIcon = null;
@@ -1362,38 +1077,6 @@ public class MenuBuilder implements Menu {
mCurrentMenuInfo = menuInfo;
}
- /**
- * Gets an adapter for providing items and their views.
- *
- * @param menuType The type of menu to get an adapter for.
- * @return A {@link MenuAdapter} for this menu with the given menu type.
- */
- public MenuAdapter getMenuAdapter(int menuType) {
- MenuAdapter adapter = mAdapterCache[menuType] == null ?
- null : mAdapterCache[menuType].get();
- if (adapter != null) return adapter;
-
- adapter = new MenuAdapter(menuType);
- mAdapterCache[menuType] = new WeakReference<MenuAdapter>(adapter);
- return adapter;
- }
-
- /**
- * Gets an adapter for providing overflow (non-action) items and their views.
- *
- * @param menuType The type of menu to get an adapter for.
- * @return A {@link MenuAdapter} for this menu with the given menu type.
- */
- public MenuAdapter getOverflowMenuAdapter(int menuType) {
- OverflowMenuAdapter adapter = mOverflowAdapterCache[menuType] == null ?
- null : mOverflowAdapterCache[menuType].get();
- if (adapter != null) return adapter;
-
- adapter = new OverflowMenuAdapter(menuType);
- mOverflowAdapterCache[menuType] = new WeakReference<OverflowMenuAdapter>(adapter);
- return adapter;
- }
-
void setOptionalIconsVisible(boolean visible) {
mOptionalIconsVisible = visible;
}
@@ -1402,108 +1085,41 @@ public class MenuBuilder implements Menu {
return mOptionalIconsVisible;
}
- public void saveHierarchyState(Bundle outState) {
- SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
-
- MenuType[] menuTypes = mMenuTypes;
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (menuTypes[i] == null) {
- continue;
- }
+ public boolean expandItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty()) return false;
- if (menuTypes[i].hasMenuView()) {
- ((View) menuTypes[i].getMenuView(null)).saveHierarchyState(viewStates);
- }
- }
-
- outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
- }
+ boolean expanded = false;
- public void restoreHierarchyState(Bundle inState) {
- // Save this for menu views opened later
- SparseArray<Parcelable> viewStates = mFrozenViewStates = inState
- .getSparseParcelableArray(VIEWS_TAG);
-
- // Thaw those menu views already open
- MenuType[] menuTypes = mMenuTypes;
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (menuTypes[i] == null) {
- continue;
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((expanded = presenter.expandItemActionView(this, item))) {
+ break;
}
-
- if (menuTypes[i].hasMenuView()) {
- ((View) menuTypes[i].getMenuView(null)).restoreHierarchyState(viewStates);
- }
- }
- }
-
- /**
- * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
- * source. This adapter will use only the visible/shown items from the menu.
- */
- public class MenuAdapter extends BaseAdapter {
- private int mMenuType;
-
- public MenuAdapter(int menuType) {
- mMenuType = menuType;
- }
-
- public int getOffset() {
- if (mMenuType == TYPE_EXPANDED) {
- return getNumIconMenuItemsShown();
- } else {
- return 0;
- }
- }
-
- public int getCount() {
- return getVisibleItems().size() - getOffset();
}
+ startDispatchingItemsChanged();
- public MenuItemImpl getItem(int position) {
- return getVisibleItems().get(position + getOffset());
- }
+ return expanded;
+ }
- public long getItemId(int position) {
- // Since a menu item's ID is optional, we'll use the position as an
- // ID for the item in the AdapterView
- return position;
- }
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty()) return false;
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView != null) {
- MenuView.ItemView itemView = (MenuView.ItemView) convertView;
- itemView.getItemData().setItemView(mMenuType, null);
+ boolean collapsed = false;
- MenuItemImpl item = (MenuItemImpl) getItem(position);
- itemView.initialize(item, mMenuType);
- item.setItemView(mMenuType, itemView);
- return convertView;
- } else {
- MenuItemImpl item = (MenuItemImpl) getItem(position);
- item.setItemView(mMenuType, null);
- return item.getItemView(mMenuType, parent);
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
+ break;
}
}
- }
+ startDispatchingItemsChanged();
- /**
- * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
- * source for overflow menu items that do not fit in the list of action items.
- */
- private class OverflowMenuAdapter extends MenuAdapter {
- public OverflowMenuAdapter(int menuType) {
- super(menuType);
- }
-
- @Override
- public MenuItemImpl getItem(int position) {
- return getNonActionItems(true).get(position);
- }
-
- @Override
- public int getCount() {
- return getNonActionItems(true).size();
- }
+ return collapsed;
}
}
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index d7438d6..5c8e057 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -24,17 +24,20 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
-import android.widget.ListAdapter;
/**
* Helper for menus that appear as Dialogs (context and submenus).
*
* @hide
*/
-public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogInterface.OnClickListener {
+public class MenuDialogHelper implements DialogInterface.OnKeyListener,
+ DialogInterface.OnClickListener,
+ DialogInterface.OnDismissListener,
+ MenuPresenter.Callback {
private MenuBuilder mMenu;
- private ListAdapter mAdapter;
private AlertDialog mDialog;
+ ListMenuPresenter mPresenter;
+ private MenuPresenter.Callback mPresenterCallback;
public MenuDialogHelper(MenuBuilder menu) {
mMenu = menu;
@@ -49,12 +52,15 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
// Many references to mMenu, create local reference
final MenuBuilder menu = mMenu;
- // Get an adapter for the menu item views
- mAdapter = menu.getMenuAdapter(MenuBuilder.TYPE_DIALOG);
-
// Get the builder for the dialog
- final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext())
- .setAdapter(mAdapter, this);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext());
+
+ mPresenter = new ListMenuPresenter(builder.getContext(),
+ com.android.internal.R.layout.list_menu_item_layout);
+
+ mPresenter.setCallback(this);
+ mMenu.addMenuPresenter(mPresenter);
+ builder.setAdapter(mPresenter.getAdapter(), this);
// Set the title
final View headerView = menu.getHeaderView();
@@ -68,13 +74,10 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
// Set the key listener
builder.setOnKeyListener(this);
-
- // Since this is for a menu, disable the recycling of views
- // This is done by the menu framework anyway
- builder.setRecycleOnMeasureEnabled(false);
// Show the menu
mDialog = builder.create();
+ mDialog.setOnDismissListener(this);
WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -122,6 +125,10 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
}
+ public void setPresenterCallback(MenuPresenter.Callback cb) {
+ mPresenterCallback = cb;
+ }
+
/**
* Dismisses the menu's dialog.
*
@@ -132,9 +139,31 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
mDialog.dismiss();
}
}
-
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mPresenter.onCloseMenu(mMenu, true);
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (allMenusAreClosing || menu == mMenu) {
+ dismiss();
+ }
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (mPresenterCallback != null) {
+ return mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return false;
+ }
+
public void onClick(DialogInterface dialog, int which) {
- mMenu.performItemAction((MenuItemImpl) mAdapter.getItem(which), 0);
+ mMenu.performItemAction((MenuItemImpl) mPresenter.getAdapter().getItem(which), 0);
}
-
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 305115f..1a6cc54 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,21 +16,20 @@
package com.android.internal.view.menu;
-import java.lang.ref.WeakReference;
+import com.android.internal.view.menu.MenuView.ItemView;
import android.content.ActivityNotFoundException;
+import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Log;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
-
-import com.android.internal.view.menu.MenuView.ItemView;
+import android.widget.LinearLayout;
/**
* @hide
@@ -60,9 +59,6 @@ public final class MenuItemImpl implements MenuItem {
* needed).
*/
private int mIconResId = NO_ICON;
-
- /** The (cached) menu item views for this item */
- private WeakReference<ItemView> mItemViews[];
/** The menu to which this item belongs */
private MenuBuilder mMenu;
@@ -83,6 +79,8 @@ public final class MenuItemImpl implements MenuItem {
private int mShowAsAction = SHOW_AS_ACTION_NEVER;
private View mActionView;
+ private OnActionExpandListener mOnActionExpandListener;
+ private boolean mIsActionViewExpanded = false;
/** Used for the icon resource ID if this item does not have an icon */
static final int NO_ICON = 0;
@@ -128,7 +126,6 @@ public final class MenuItemImpl implements MenuItem {
com.android.internal.R.string.menu_space_shortcut_label);
}
- mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
mMenu = menu;
mId = id;
mGroup = group;
@@ -149,9 +146,7 @@ public final class MenuItemImpl implements MenuItem {
return true;
}
- MenuBuilder.Callback callback = mMenu.getCallback();
- if (callback != null &&
- callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
+ if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
return true;
}
@@ -172,10 +167,6 @@ public final class MenuItemImpl implements MenuItem {
return false;
}
- private boolean hasItemView(int menuType) {
- return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
- }
-
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
@@ -187,13 +178,7 @@ public final class MenuItemImpl implements MenuItem {
mFlags &= ~ENABLED;
}
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // If the item view prefers a condensed title, only set this title if there
- // is no condensed title for this item
- if (hasItemView(i)) {
- mItemViews[i].get().setEnabled(enabled);
- }
- }
+ mMenu.onItemsChanged(false);
return this;
}
@@ -242,7 +227,7 @@ public final class MenuItemImpl implements MenuItem {
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -256,7 +241,7 @@ public final class MenuItemImpl implements MenuItem {
mShortcutNumericChar = numericChar;
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -265,7 +250,7 @@ public final class MenuItemImpl implements MenuItem {
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -322,38 +307,6 @@ public final class MenuItemImpl implements MenuItem {
return mMenu.isShortcutsVisible() && (getShortcut() != 0);
}
- /**
- * Refreshes the shortcut shown on the ItemViews. This method retrieves current
- * shortcut state (mode and shown) from the menu that contains this item.
- */
- private void refreshShortcutOnItemViews() {
- refreshShortcutOnItemViews(mMenu.isShortcutsVisible(), mMenu.isQwertyMode());
- }
-
- /**
- * Refreshes the shortcut shown on the ItemViews. This is usually called by
- * the {@link MenuBuilder} when it is refreshing the shortcuts on all item
- * views, so it passes arguments rather than each item calling a method on the menu to get
- * the same values.
- *
- * @param menuShortcutShown The menu's shortcut shown mode. In addition,
- * this method will ensure this item has a shortcut before it
- * displays the shortcut.
- * @param isQwertyMode Whether the shortcut mode is qwerty mode
- */
- void refreshShortcutOnItemViews(boolean menuShortcutShown, boolean isQwertyMode) {
- final char shortcutKey = (isQwertyMode) ? mShortcutAlphabeticChar : mShortcutNumericChar;
-
- // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
- final boolean showShortcut = menuShortcutShown && (shortcutKey != 0);
-
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setShortcut(showShortcut, shortcutKey);
- }
- }
- }
-
public SubMenu getSubMenu() {
return mSubMenu;
}
@@ -394,18 +347,7 @@ public final class MenuItemImpl implements MenuItem {
public MenuItem setTitle(CharSequence title) {
mTitle = title;
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // If the item view prefers a condensed title, only set this title if there
- // is no condensed title for this item
- if (!hasItemView(i)) {
- continue;
- }
-
- ItemView itemView = mItemViews[i].get();
- if (!itemView.prefersCondensedTitle() || mTitleCondensed == null) {
- itemView.setTitle(title);
- }
- }
+ mMenu.onItemsChanged(false);
if (mSubMenu != null) {
mSubMenu.setHeaderTitle(title);
@@ -430,18 +372,12 @@ public final class MenuItemImpl implements MenuItem {
title = mTitle;
}
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // Refresh those item views that prefer a condensed title
- if (hasItemView(i) && (mItemViews[i].get().prefersCondensedTitle())) {
- mItemViews[i].get().setTitle(title);
- }
- }
+ mMenu.onItemsChanged(false);
return this;
}
public Drawable getIcon() {
-
if (mIconDrawable != null) {
return mIconDrawable;
}
@@ -456,7 +392,7 @@ public final class MenuItemImpl implements MenuItem {
public MenuItem setIcon(Drawable icon) {
mIconResId = NO_ICON;
mIconDrawable = icon;
- setIconOnViews(icon);
+ mMenu.onItemsChanged(false);
return this;
}
@@ -466,33 +402,10 @@ public final class MenuItemImpl implements MenuItem {
mIconResId = iconResId;
// If we have a view, we need to push the Drawable to them
- if (haveAnyOpenedIconCapableItemViews()) {
- Drawable drawable = iconResId != NO_ICON ? mMenu.getResources().getDrawable(iconResId)
- : null;
- setIconOnViews(drawable);
- }
+ mMenu.onItemsChanged(false);
return this;
}
-
- private void setIconOnViews(Drawable icon) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // Refresh those item views that are able to display an icon
- if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
- mItemViews[i].get().setIcon(icon);
- }
- }
- }
-
- private boolean haveAnyOpenedIconCapableItemViews() {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
- return true;
- }
- }
-
- return false;
- }
public boolean isCheckable() {
return (mFlags & CHECKABLE) == CHECKABLE;
@@ -502,19 +415,14 @@ public final class MenuItemImpl implements MenuItem {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
if (oldFlags != mFlags) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setCheckable(checkable);
- }
- }
+ mMenu.onItemsChanged(false);
}
return this;
}
- public void setExclusiveCheckable(boolean exclusive)
- {
- mFlags = (mFlags&~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+ public void setExclusiveCheckable(boolean exclusive) {
+ mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
}
public boolean isExclusiveCheckable() {
@@ -541,11 +449,7 @@ public final class MenuItemImpl implements MenuItem {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
if (oldFlags != mFlags) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setChecked(checked);
- }
- }
+ mMenu.onItemsChanged(false);
}
}
@@ -581,39 +485,6 @@ public final class MenuItemImpl implements MenuItem {
mClickListener = clickListener;
return this;
}
-
- View getItemView(int menuType, ViewGroup parent) {
- if (!hasItemView(menuType)) {
- mItemViews[menuType] = new WeakReference<ItemView>(createItemView(menuType, parent));
- }
-
- return (View) mItemViews[menuType].get();
- }
-
- void setItemView(int menuType, ItemView view) {
- mItemViews[menuType] = new WeakReference<ItemView>(view);
- }
-
- /**
- * Create and initializes a menu item view that implements {@link MenuView.ItemView}.
- * @param menuType The type of menu to get a View for (must be one of
- * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
- * {@link MenuBuilder#TYPE_SUB}, {@link MenuBuilder#TYPE_CONTEXT}).
- * @return The inflated {@link MenuView.ItemView} that is ready for use
- */
- private MenuView.ItemView createItemView(int menuType, ViewGroup parent) {
- // Create the MenuView
- MenuView.ItemView itemView = (MenuView.ItemView) getLayoutInflater(menuType)
- .inflate(MenuBuilder.ITEM_LAYOUT_RES_FOR_TYPE[menuType], parent, false);
- itemView.initialize(this, menuType);
- return itemView;
- }
-
- void clearItemViews() {
- for (int i = mItemViews.length - 1; i >= 0; i--) {
- mItemViews[i] = null;
- }
- }
@Override
public String toString() {
@@ -627,24 +498,12 @@ public final class MenuItemImpl implements MenuItem {
public ContextMenuInfo getMenuInfo() {
return mMenuInfo;
}
-
- /**
- * Returns a LayoutInflater that is themed for the given menu type.
- *
- * @param menuType The type of menu.
- * @return A LayoutInflater.
- */
- public LayoutInflater getLayoutInflater(int menuType) {
- return mMenu.getMenuType(menuType).getInflater();
- }
/**
- * @return Whether the given menu type should show icons for menu items.
+ * @return Whether the menu should show icons for menu items.
*/
- public boolean shouldShowIcon(int menuType) {
- return menuType == MenuBuilder.TYPE_ICON ||
- menuType == MenuBuilder.TYPE_ACTION_BUTTON ||
- mMenu.getOptionalIconsVisible();
+ public boolean shouldShowIcon() {
+ return mMenu.getOptionalIconsVisible();
}
public boolean isActionButton() {
@@ -668,7 +527,9 @@ public final class MenuItemImpl implements MenuItem {
}
public boolean showsTextAsAction() {
- return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
+ return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT &&
+ mMenu.getContext().getResources().getBoolean(
+ com.android.internal.R.bool.allow_action_menu_item_text_with_icon);
}
public void setShowAsAction(int actionEnum) {
@@ -695,13 +556,70 @@ public final class MenuItemImpl implements MenuItem {
}
public MenuItem setActionView(int resId) {
- LayoutInflater inflater = LayoutInflater.from(mMenu.getContext());
- ViewGroup parent = (ViewGroup) mMenu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, null);
- setActionView(inflater.inflate(resId, parent, false));
+ final Context context = mMenu.getContext();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ setActionView(inflater.inflate(resId, new LinearLayout(context)));
return this;
}
public View getActionView() {
return mActionView;
}
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ setShowAsAction(actionEnum);
+ return this;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
+ return false;
+ }
+
+ if (mOnActionExpandListener == null ||
+ mOnActionExpandListener.onMenuItemActionExpand(this)) {
+ return mMenu.expandItemActionView(this);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
+ return false;
+ }
+ if (mActionView == null) {
+ // We're already collapsed if we have no action view.
+ return true;
+ }
+
+ if (mOnActionExpandListener == null ||
+ mOnActionExpandListener.onMenuItemActionCollapse(this)) {
+ return mMenu.collapseItemActionView(this);
+ }
+
+ return false;
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ mOnActionExpandListener = listener;
+ return this;
+ }
+
+ public boolean hasCollapsibleActionView() {
+ return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
+ }
+
+ public void setActionViewExpanded(boolean isExpanded) {
+ mIsActionViewExpanded = isExpanded;
+ mMenu.onItemsChanged(false);
+ }
+
+ public boolean isActionViewExpanded() {
+ return mIsActionViewExpanded;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 04a059e..5767519 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -16,31 +16,35 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
-
import android.content.Context;
-import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
-import android.view.MenuItem;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
import android.widget.ListPopupWindow;
import android.widget.PopupWindow;
-import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
+ * Presents a menu as a small, simple popup anchored to another view.
* @hide
*/
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
- View.OnAttachStateChangeListener {
+ View.OnAttachStateChangeListener, MenuPresenter {
private static final String TAG = "MenuPopupHelper";
+ static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
+
private Context mContext;
+ private LayoutInflater mInflater;
private ListPopupWindow mPopup;
private MenuBuilder mMenu;
private int mPopupMaxWidth;
@@ -48,7 +52,9 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
private boolean mOverflowOnly;
private ViewTreeObserver mTreeObserver;
- private final Handler mHandler = new Handler();
+ private MenuAdapter mAdapter;
+
+ private Callback mPresenterCallback;
public MenuPopupHelper(Context context, MenuBuilder menu) {
this(context, menu, null, false);
@@ -61,6 +67,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
public MenuPopupHelper(Context context, MenuBuilder menu,
View anchorView, boolean overflowOnly) {
mContext = context;
+ mInflater = LayoutInflater.from(context);
mMenu = menu;
mOverflowOnly = overflowOnly;
@@ -68,6 +75,8 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
mPopupMaxWidth = metrics.widthPixels / 2;
mAnchorView = anchorView;
+
+ menu.addMenuPresenter(this);
}
public void setAnchorView(View anchor) {
@@ -82,23 +91,14 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
public boolean tryShow() {
mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
- mPopup.setOnItemClickListener(this);
mPopup.setOnDismissListener(this);
+ mPopup.setOnItemClickListener(this);
- final MenuAdapter adapter = mOverflowOnly ?
- mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) :
- mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP);
- mPopup.setAdapter(adapter);
+ mAdapter = new MenuAdapter(mMenu);
+ mPopup.setAdapter(mAdapter);
mPopup.setModal(true);
View anchor = mAnchorView;
- if (anchor == null && mMenu instanceof SubMenuBuilder) {
- SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
- final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
- anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null);
- mAnchorView = anchor;
- }
-
if (anchor != null) {
final boolean addGlobalListener = mTreeObserver == null;
mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
@@ -109,7 +109,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return false;
}
- mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth));
+ mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
mPopup.show();
mPopup.getListView().setOnKeyListener(this);
@@ -136,23 +136,10 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return mPopup != null && mPopup.isShowing();
}
+ @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (!isShowing()) return;
-
- MenuItem item = null;
- if (mOverflowOnly) {
- item = mMenu.getOverflowItem(position);
- } else {
- item = mMenu.getVisibleItems().get(position);
- }
- dismiss();
-
- final MenuItem performItem = item;
- mHandler.post(new Runnable() {
- public void run() {
- mMenu.performItemAction(performItem, 0);
- }
- });
+ MenuAdapter adapter = mAdapter;
+ adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
public boolean onKey(View v, int keyCode, KeyEvent event) {
@@ -163,7 +150,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return false;
}
- private int measureContentWidth(MenuAdapter adapter) {
+ private int measureContentWidth(ListAdapter adapter) {
// Menus don't tend to be long, so this is more sane than it looks.
int width = 0;
View itemView = null;
@@ -211,4 +198,99 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
}
v.removeOnAttachStateChangeListener(this);
}
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Don't need to do anything; we added as a presenter in the constructor.
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mAdapter != null) mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (subMenu.hasVisibleItems()) {
+ MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
+ subPopup.setCallback(mPresenterCallback);
+ if (subPopup.tryShow()) {
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ // Only care about the (sub)menu we're presenting.
+ if (menu != mMenu) return;
+
+ dismiss();
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ private class MenuAdapter extends BaseAdapter {
+ private MenuBuilder mAdapterMenu;
+
+ public MenuAdapter(MenuBuilder menu) {
+ mAdapterMenu = menu;
+ }
+
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ return items.size();
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ return items.get(position);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
new file mode 100644
index 0000000..bd66448
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.view.Menu;
+import android.view.ViewGroup;
+
+/**
+ * A MenuPresenter is responsible for building views for a Menu object.
+ * It takes over some responsibility from the old style monolithic MenuBuilder class.
+ */
+public interface MenuPresenter {
+ /**
+ * Called by menu implementation to notify another component of open/close events.
+ */
+ public interface Callback {
+ /**
+ * Called when a menu is closing.
+ * @param menu
+ * @param allMenusAreClosing
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called when a submenu opens. Useful for notifying the application
+ * of menu state so that it does not attempt to hide the action bar
+ * while a submenu is open or similar.
+ *
+ * @param subMenu Submenu currently being opened
+ * @return true if the Callback will handle presenting the submenu, false if
+ * the presenter should attempt to do so.
+ */
+ public boolean onOpenSubMenu(MenuBuilder subMenu);
+ }
+
+ /**
+ * Initialize this presenter for the given context and menu.
+ * This method is called by MenuBuilder when a presenter is
+ * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+ *
+ * @param context Context for this presenter; used for view creation and resource management
+ * @param menu Menu to host
+ */
+ public void initForMenu(Context context, MenuBuilder menu);
+
+ /**
+ * Retrieve a MenuView to display the menu specified in
+ * {@link #initForMenu(Context, Menu)}.
+ *
+ * @param root Intended parent of the MenuView.
+ * @return A freshly created MenuView.
+ */
+ public MenuView getMenuView(ViewGroup root);
+
+ /**
+ * Update the menu UI in response to a change. Called by
+ * MenuBuilder during the normal course of operation.
+ *
+ * @param cleared true if the menu was entirely cleared
+ */
+ public void updateMenuView(boolean cleared);
+
+ /**
+ * Set a callback object that will be notified of menu events
+ * related to this specific presentation.
+ * @param cb Callback that will be notified of future events
+ */
+ public void setCallback(Callback cb);
+
+ /**
+ * Called by Menu implementations to indicate that a submenu item
+ * has been selected. An active Callback should be notified, and
+ * if applicable the presenter should present the submenu.
+ *
+ * @param subMenu SubMenu being opened
+ * @return true if the the event was handled, false otherwise.
+ */
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+ /**
+ * Called by Menu implementations to indicate that a menu or submenu is
+ * closing. Presenter implementations should close the representation
+ * of the menu indicated as necessary and notify a registered callback.
+ *
+ * @param menu Menu or submenu that is closing.
+ * @param allMenusAreClosing True if all associated menus are closing.
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called by Menu implementations to flag items that will be shown as actions.
+ * @return true if this presenter changed the action status of any items.
+ */
+ public boolean flagActionItems();
+
+ /**
+ * Called when a menu item with a collapsable action view should expand its action view.
+ *
+ * @param menu Menu containing the item to be expanded
+ * @param item Item to be expanded
+ * @return true if this presenter expanded the action view, false otherwise.
+ */
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Called when a menu item with a collapsable action view should collapse its action view.
+ *
+ * @param menu Menu containing the item to be collapsed
+ * @param item Item to be collapsed
+ * @return true if this presenter collapsed the action view, false otherwise.
+ */
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item);
+}
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
index 5090400..407caae 100644
--- a/core/java/com/android/internal/view/menu/MenuView.java
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -22,7 +22,7 @@ import com.android.internal.view.menu.MenuItemImpl;
import android.graphics.drawable.Drawable;
/**
- * Minimal interface for a menu view. {@link #initialize(MenuBuilder, int)} must be called for the
+ * Minimal interface for a menu view. {@link #initialize(MenuBuilder)} must be called for the
* menu to be functional.
*
* @hide
@@ -33,18 +33,8 @@ public interface MenuView {
* view is inflated.
*
* @param menu The menu that this MenuView should display.
- * @param menuType The type of this menu, one of
- * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
- * {@link MenuBuilder#TYPE_DIALOG}).
*/
- public void initialize(MenuBuilder menu, int menuType);
-
- /**
- * Forces the menu view to update its view to reflect the new state of the menu.
- *
- * @param cleared Whether the menu was cleared or just modified.
- */
- public void updateChildren(boolean cleared);
+ public void initialize(MenuBuilder menu);
/**
* Returns the default animations to be used for this menu when entering/exiting.
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index af1b996..fb1cd5e 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -67,11 +67,6 @@ public class SubMenuBuilder extends MenuBuilder implements SubMenu {
}
@Override
- public Callback getCallback() {
- return mParentMenu.getCallback();
- }
-
- @Override
public void setCallback(Callback callback) {
mParentMenu.setCallback(callback);
}
@@ -81,6 +76,12 @@ public class SubMenuBuilder extends MenuBuilder implements SubMenu {
return mParentMenu;
}
+ @Override
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return super.dispatchMenuItemSelected(menu, item) ||
+ mParentMenu.dispatchMenuItemSelected(menu, item);
+ }
+
public SubMenu setIcon(Drawable icon) {
mItem.setIcon(icon);
return this;
@@ -110,5 +111,14 @@ public class SubMenuBuilder extends MenuBuilder implements SubMenu {
public SubMenu setHeaderView(View view) {
return (SubMenu) super.setHeaderViewInt(view);
}
-
+
+ @Override
+ public boolean expandItemActionView(MenuItemImpl item) {
+ return mParentMenu.expandItemActionView(item);
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ return mParentMenu.collapseItemActionView(item);
+ }
}
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
new file mode 100644
index 0000000..ccbce3e
--- /dev/null
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget;
+
+import com.android.internal.view.menu.ActionMenuPresenter;
+import com.android.internal.view.menu.ActionMenuView;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+
+public abstract class AbsActionBarView extends ViewGroup {
+ protected ActionMenuView mMenuView;
+ protected ActionMenuPresenter mActionMenuPresenter;
+ protected ActionBarContainer mSplitView;
+
+ protected Animator mVisibilityAnim;
+ protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
+
+ private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
+
+ private static final int FADE_DURATION = 200;
+
+ public AbsActionBarView(Context context) {
+ super(context);
+ }
+
+ public AbsActionBarView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AbsActionBarView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setSplitView(ActionBarContainer splitView) {
+ mSplitView = splitView;
+ }
+
+ public void animateToVisibility(int visibility) {
+ if (mVisibilityAnim != null) {
+ mVisibilityAnim.cancel();
+ }
+ if (visibility == VISIBLE) {
+ if (getVisibility() != VISIBLE) {
+ setAlpha(0);
+ if (mSplitView != null && mMenuView != null) {
+ mMenuView.setAlpha(0);
+ }
+ }
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
+ anim.setDuration(FADE_DURATION);
+ anim.setInterpolator(sAlphaInterpolator);
+ if (mSplitView != null && mMenuView != null) {
+ AnimatorSet set = new AnimatorSet();
+ ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 1);
+ splitAnim.setDuration(FADE_DURATION);
+ set.addListener(mVisAnimListener.withFinalVisibility(visibility));
+ set.play(anim).with(splitAnim);
+ set.start();
+ } else {
+ anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
+ anim.start();
+ }
+ } else {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
+ anim.setDuration(FADE_DURATION);
+ anim.setInterpolator(sAlphaInterpolator);
+ if (mSplitView != null && mMenuView != null) {
+ AnimatorSet set = new AnimatorSet();
+ ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 0);
+ splitAnim.setDuration(FADE_DURATION);
+ set.addListener(mVisAnimListener.withFinalVisibility(visibility));
+ set.play(anim).with(splitAnim);
+ set.start();
+ } else {
+ anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
+ anim.start();
+ }
+ }
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ if (mVisibilityAnim != null) {
+ mVisibilityAnim.end();
+ }
+ super.setVisibility(visibility);
+ }
+
+ public boolean showOverflowMenu() {
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.showOverflowMenu();
+ }
+ return false;
+ }
+
+ public void postShowOverflowMenu() {
+ post(new Runnable() {
+ public void run() {
+ showOverflowMenu();
+ }
+ });
+ }
+
+ public boolean hideOverflowMenu() {
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.hideOverflowMenu();
+ }
+ return false;
+ }
+
+ public boolean isOverflowMenuShowing() {
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.isOverflowMenuShowing();
+ }
+ return false;
+ }
+
+ public boolean isOverflowReserved() {
+ return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
+ }
+
+ public void dismissPopupMenus() {
+ if (mActionMenuPresenter != null) {
+ mActionMenuPresenter.dismissPopupMenus();
+ }
+ }
+
+ protected int measureChildView(View child, int availableWidth, int childSpecHeight,
+ int spacing) {
+ child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ childSpecHeight);
+
+ availableWidth -= child.getMeasuredWidth();
+ availableWidth -= spacing;
+
+ return Math.max(0, availableWidth);
+ }
+
+ protected int positionChild(View child, int x, int y, int contentHeight) {
+ int childWidth = child.getMeasuredWidth();
+ int childHeight = child.getMeasuredHeight();
+ int childTop = y + (contentHeight - childHeight) / 2;
+
+ child.layout(x, childTop, x + childWidth, childTop + childHeight);
+
+ return childWidth;
+ }
+
+ protected int positionChildInverse(View child, int x, int y, int contentHeight) {
+ int childWidth = child.getMeasuredWidth();
+ int childHeight = child.getMeasuredHeight();
+ int childTop = y + (contentHeight - childHeight) / 2;
+
+ child.layout(x - childWidth, childTop, x, childTop + childHeight);
+
+ return childWidth;
+ }
+
+ protected class VisibilityAnimListener implements Animator.AnimatorListener {
+ private boolean mCanceled = false;
+ private int mFinalVisibility;
+
+ public VisibilityAnimListener withFinalVisibility(int visibility) {
+ mFinalVisibility = visibility;
+ return this;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ setVisibility(VISIBLE);
+ mVisibilityAnim = animation;
+ mCanceled = false;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceled) return;
+
+ mVisibilityAnim = null;
+ setVisibility(mFinalVisibility);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index c9b0ec9..d710cfa 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -16,10 +16,13 @@
package com.android.internal.widget;
+import android.app.ActionBar;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.view.ActionMode;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.FrameLayout;
/**
@@ -29,6 +32,8 @@ import android.widget.FrameLayout;
*/
public class ActionBarContainer extends FrameLayout {
private boolean mIsTransitioning;
+ private View mTabContainer;
+ private ActionBarView mActionBarView;
public ActionBarContainer(Context context) {
this(context, null);
@@ -43,6 +48,12 @@ public class ActionBarContainer extends FrameLayout {
a.recycle();
}
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
+ }
+
/**
* Set the action bar into a "transitioning" state. While transitioning
* the bar will block focus and touch from all of its descendants. This
@@ -65,6 +76,78 @@ public class ActionBarContainer extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
+
+ // An action bar always eats touch events.
return true;
}
+
+ public void setTabContainer(View tabView) {
+ if (mTabContainer != null) {
+ removeView(mTabContainer);
+ }
+ mTabContainer = tabView;
+ if (tabView != null) {
+ addView(tabView);
+ }
+ }
+
+ public View getTabContainer() {
+ return mTabContainer;
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
+ // No starting an action mode for an action bar child! (Where would it go?)
+ return null;
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int nonTabHeight = 0;
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+
+ if (child == mTabContainer) continue;
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ nonTabHeight = Math.max(nonTabHeight,
+ child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+ }
+
+ if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+ final int mode = MeasureSpec.getMode(heightMeasureSpec);
+ if (mode == MeasureSpec.AT_MOST) {
+ final int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(getMeasuredWidth(),
+ Math.min(nonTabHeight + mTabContainer.getMeasuredHeight(), maxHeight));
+ }
+ }
+ }
+
+ @Override
+ public void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+ final int containerHeight = getMeasuredHeight();
+ final int tabHeight = mTabContainer.getMeasuredHeight();
+
+ if ((mActionBarView.getDisplayOptions() & ActionBar.DISPLAY_SHOW_HOME) == 0) {
+ // Not showing home, put tabs on top.
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++){
+ final View child = getChildAt(i);
+
+ if (child == mTabContainer) continue;
+
+ child.offsetTopAndBottom(tabHeight);
+ }
+ mTabContainer.layout(l, 0, r, tabHeight);
+ } else {
+ mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index f762265..fc43994 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -15,29 +15,30 @@
*/
package com.android.internal.widget;
-import com.android.internal.R;
-import com.android.internal.view.menu.ActionMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuPresenter;
+import com.android.internal.view.menu.ActionMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+
/**
* @hide
*/
-public class ActionBarContextView extends ViewGroup implements AnimatorListener {
+public class ActionBarContextView extends AbsActionBarView implements AnimatorListener {
private static final String TAG = "ActionBarContextView";
private int mContentHeight;
@@ -52,7 +53,6 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
private TextView mSubtitleView;
private int mTitleStyleRes;
private int mSubtitleStyleRes;
- private ActionMenuView mMenuView;
private Animator mCurrentAnimation;
private boolean mAnimateInOnLayout;
@@ -85,12 +85,6 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
com.android.internal.R.styleable.ActionMode_height, 0);
a.recycle();
}
-
- @Override
- public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
- // No starting an action mode for an existing action mode UI child! (Where would it go?)
- return null;
- }
public void setHeight(int height) {
mContentHeight = height;
@@ -136,27 +130,24 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
- if (mTitle != null) {
- mTitleView.setText(mTitle);
- if (mTitleStyleRes != 0) {
- mTitleView.setTextAppearance(mContext, mTitleStyleRes);
- }
+ if (mTitleStyleRes != 0) {
+ mTitleView.setTextAppearance(mContext, mTitleStyleRes);
}
- if (mSubtitle != null) {
- mSubtitleView.setText(mSubtitle);
- if (mSubtitleStyleRes != 0) {
- mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
- }
- mSubtitleView.setVisibility(VISIBLE);
- }
- } else {
- mTitleView.setText(mTitle);
- mSubtitleView.setText(mSubtitle);
- mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE);
- if (mTitleLayout.getParent() == null) {
- addView(mTitleLayout);
+ if (mSubtitleStyleRes != 0) {
+ mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
}
}
+
+ mTitleView.setText(mTitle);
+ mSubtitleView.setText(mSubtitle);
+
+ final boolean hasTitle = !TextUtils.isEmpty(mTitle);
+ final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle);
+ mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE);
+ mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE);
+ if (mTitleLayout.getParent() == null) {
+ addView(mTitleLayout);
+ }
}
public void initForMode(final ActionMode mode) {
@@ -176,10 +167,28 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
});
final MenuBuilder menu = (MenuBuilder) mode.getMenu();
- mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this);
- mMenuView.setOverflowReserved(true);
- mMenuView.updateChildren(false);
- addView(mMenuView);
+ mActionMenuPresenter = new ActionMenuPresenter();
+ mActionMenuPresenter.setReserveOverflow(true);
+
+ final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT);
+ if (mSplitView == null) {
+ menu.addMenuPresenter(mActionMenuPresenter);
+ mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
+ addView(mMenuView, layoutParams);
+ } else {
+ // Allow full screen width in split mode.
+ mActionMenuPresenter.setWidthLimit(
+ getContext().getResources().getDisplayMetrics().widthPixels, true);
+ // No limit to the item count; use whatever will fit.
+ mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
+ // Span the whole width
+ layoutParams.width = LayoutParams.MATCH_PARENT;
+ layoutParams.height = mContentHeight;
+ menu.addMenuPresenter(mActionMenuPresenter);
+ mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
+ mSplitView.addView(mMenuView, layoutParams);
+ }
mAnimateInOnLayout = true;
}
@@ -211,34 +220,34 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
public void killMode() {
finishAnimation();
removeAllViews();
+ if (mSplitView != null) {
+ mSplitView.removeView(mMenuView);
+ }
mCustomView = null;
mMenuView = null;
mAnimateInOnLayout = false;
}
+ @Override
public boolean showOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.showOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.showOverflowMenu();
}
return false;
}
- public void openOverflowMenu() {
- if (mMenuView != null) {
- mMenuView.openOverflowMenu();
- }
- }
-
+ @Override
public boolean hideOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.hideOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.hideOverflowMenu();
}
return false;
}
+ @Override
public boolean isOverflowMenuShowing() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuShowing();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.isOverflowMenuShowing();
}
return false;
}
@@ -346,7 +355,7 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
private Animator makeOutAnimation() {
ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX",
- 0, -mClose.getWidth());
+ -mClose.getWidth());
buttonAnimator.setDuration(200);
buttonAnimator.addListener(this);
buttonAnimator.setInterpolator(new DecelerateInterpolator());
@@ -360,7 +369,7 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
for (int i = 0; i < 0; i++) {
View child = mMenuView.getChildAt(i);
child.setScaleY(0);
- ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 1, 0);
+ ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0);
a.setDuration(100);
a.setStartDelay(i * 70);
b.with(a);
@@ -387,7 +396,7 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
mAnimateInOnLayout = false;
}
}
-
+
if (mTitleLayout != null && mCustomView == null) {
x += positionChild(mTitleLayout, x, y, contentHeight);
}
@@ -403,36 +412,6 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
}
}
- private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
- child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
- childSpecHeight);
-
- availableWidth -= child.getMeasuredWidth();
- availableWidth -= spacing;
-
- return Math.max(0, availableWidth);
- }
-
- private int positionChild(View child, int x, int y, int contentHeight) {
- int childWidth = child.getMeasuredWidth();
- int childHeight = child.getMeasuredHeight();
- int childTop = y + (contentHeight - childHeight) / 2;
-
- child.layout(x, childTop, x + childWidth, childTop + childHeight);
-
- return childWidth;
- }
-
- private int positionChildInverse(View child, int x, int y, int contentHeight) {
- int childWidth = child.getMeasuredWidth();
- int childHeight = child.getMeasuredHeight();
- int childTop = y + (contentHeight - childHeight) / 2;
-
- child.layout(x - childWidth, childTop, x, childTop + childHeight);
-
- return childWidth;
- }
-
@Override
public void onAnimationStart(Animator animation) {
}
@@ -452,4 +431,9 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
@Override
public void onAnimationRepeat(Animator animation) {
}
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 891557d..e3286dd 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -18,8 +18,13 @@ package com.android.internal.widget;
import com.android.internal.R;
import com.android.internal.view.menu.ActionMenuItem;
+import com.android.internal.view.menu.ActionMenuPresenter;
import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
@@ -28,25 +33,25 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.text.TextUtils;
-import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.ActionMode;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@@ -57,7 +62,7 @@ import android.widget.TextView;
/**
* @hide
*/
-public class ActionBarView extends ViewGroup {
+public class ActionBarView extends AbsActionBarView {
private static final String TAG = "ActionBarView";
/**
@@ -77,7 +82,7 @@ public class ActionBarView extends ViewGroup {
private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
- private final int mContentHeight;
+ private int mContentHeight;
private int mNavigationMode;
private int mDisplayOptions = ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP;
@@ -85,18 +90,15 @@ public class ActionBarView extends ViewGroup {
private CharSequence mSubtitle;
private Drawable mIcon;
private Drawable mLogo;
- private Drawable mDivider;
- private View mHomeLayout;
- private View mHomeAsUpView;
- private ImageView mIconView;
+ private HomeView mHomeLayout;
+ private HomeView mExpandedHomeLayout;
private LinearLayout mTitleLayout;
private TextView mTitleView;
private TextView mSubtitleView;
private Spinner mSpinner;
private LinearLayout mListNavLayout;
- private HorizontalScrollView mTabScrollView;
- private LinearLayout mTabLayout;
+ private ScrollingTabContainerView mTabScrollView;
private View mCustomNavView;
private ProgressBar mProgressView;
private ProgressBar mIndeterminateProgressView;
@@ -109,11 +111,12 @@ public class ActionBarView extends ViewGroup {
private int mProgressStyle;
private int mIndeterminateProgressStyle;
- private boolean mShowMenu;
+ private boolean mSplitActionBar;
private boolean mUserTitle;
+ private boolean mIncludeTabs;
+ private boolean mIsCollapsable;
private MenuBuilder mOptionsMenu;
- private ActionMenuView mMenuView;
private ActionBarContextView mContextView;
@@ -122,6 +125,11 @@ public class ActionBarView extends ViewGroup {
private SpinnerAdapter mSpinnerAdapter;
private OnNavigationListener mCallback;
+ private Runnable mTabSelector;
+
+ private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
+ View mExpandedActionView;
+
private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView parent, View view, int position, long id) {
@@ -134,7 +142,15 @@ public class ActionBarView extends ViewGroup {
}
};
- private OnClickListener mTabClickListener = null;
+ private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final MenuItemImpl item = mExpandedMenuPresenter.mCurrentExpandedItem;
+ if (item != null) {
+ item.collapseActionView();
+ }
+ }
+ };
public ActionBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -185,10 +201,11 @@ public class ActionBarView extends ViewGroup {
com.android.internal.R.styleable.ActionBar_homeLayout,
com.android.internal.R.layout.action_bar_home);
- mHomeLayout = inflater.inflate(homeResId, this, false);
+ mHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
- mHomeAsUpView = mHomeLayout.findViewById(com.android.internal.R.id.up);
- mIconView = (ImageView) mHomeLayout.findViewById(com.android.internal.R.id.home);
+ mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
+ mExpandedHomeLayout.setUp(true);
+ mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener);
mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
@@ -210,8 +227,6 @@ public class ActionBarView extends ViewGroup {
mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
- mDivider = a.getDrawable(R.styleable.ActionBar_divider);
-
a.recycle();
mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
@@ -228,6 +243,17 @@ public class ActionBarView extends ViewGroup {
mHomeLayout.setFocusable(true);
}
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeCallbacks(mTabSelector);
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
public void initProgress() {
mProgressView = new ProgressBar(mContext, null, 0, mProgressStyle);
mProgressView.setId(R.id.progress_horizontal);
@@ -242,77 +268,97 @@ public class ActionBarView extends ViewGroup {
addView(mIndeterminateProgressView);
}
- @Override
- public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
- // No starting an action mode for an action bar child! (Where would it go?)
- return null;
+ public void setContentHeight(int height) {
+ mContentHeight = height;
+ requestLayout();
}
- public void setCallback(OnNavigationListener callback) {
- mCallback = callback;
+ public int getContentHeight() {
+ return mContentHeight;
}
- public void setMenu(Menu menu) {
- if (menu == mOptionsMenu) return;
-
- MenuBuilder builder = (MenuBuilder) menu;
- mOptionsMenu = builder;
- if (mMenuView != null) {
- removeView(mMenuView);
+ public void setSplitActionBar(boolean splitActionBar) {
+ if (mSplitActionBar != splitActionBar) {
+ if (mMenuView != null) {
+ if (splitActionBar) {
+ removeView(mMenuView);
+ if (mSplitView != null) {
+ mSplitView.addView(mMenuView);
+ }
+ } else {
+ addView(mMenuView);
+ }
+ }
+ mSplitActionBar = splitActionBar;
}
- final ActionMenuView menuView = (ActionMenuView) builder.getMenuView(
- MenuBuilder.TYPE_ACTION_BUTTON, null);
- final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT);
- menuView.setLayoutParams(layoutParams);
- addView(menuView);
- mMenuView = menuView;
}
- public boolean showOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.showOverflowMenu();
- }
- return false;
+ public boolean isSplitActionBar() {
+ return mSplitActionBar;
}
- public void openOverflowMenu() {
- if (mMenuView != null) {
- mMenuView.openOverflowMenu();
- }
+ public boolean hasEmbeddedTabs() {
+ return mIncludeTabs;
}
- public void postShowOverflowMenu() {
- post(new Runnable() {
- public void run() {
- showOverflowMenu();
- }
- });
+ public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
+ mTabScrollView = tabs;
+ mIncludeTabs = tabs != null;
+ if (mIncludeTabs && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
+ addView(mTabScrollView);
+ }
}
- public boolean hideOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.hideOverflowMenu();
- }
- return false;
+ public void setCallback(OnNavigationListener callback) {
+ mCallback = callback;
}
- public boolean isOverflowMenuShowing() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuShowing();
+ public void setMenu(Menu menu, MenuPresenter.Callback cb) {
+ if (menu == mOptionsMenu) return;
+
+ if (mOptionsMenu != null) {
+ mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
+ mOptionsMenu.removeMenuPresenter(mExpandedMenuPresenter);
}
- return false;
- }
- public boolean isOverflowMenuOpen() {
+ MenuBuilder builder = (MenuBuilder) menu;
+ mOptionsMenu = builder;
if (mMenuView != null) {
- return mMenuView.isOverflowMenuOpen();
+ removeView(mMenuView);
+ }
+ if (mActionMenuPresenter == null) {
+ mActionMenuPresenter = new ActionMenuPresenter();
+ mActionMenuPresenter.setCallback(cb);
+ mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
}
- return false;
- }
- public boolean isOverflowReserved() {
- return mMenuView != null && mMenuView.isOverflowReserved();
+ ActionMenuView menuView;
+ final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT);
+ if (!mSplitActionBar) {
+ builder.addMenuPresenter(mActionMenuPresenter);
+ builder.addMenuPresenter(mExpandedMenuPresenter);
+ menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
+ addView(menuView, layoutParams);
+ } else {
+ // Allow full screen width in split mode.
+ mActionMenuPresenter.setWidthLimit(
+ getContext().getResources().getDisplayMetrics().widthPixels, true);
+ // No limit to the item count; use whatever will fit.
+ mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
+ // Span the whole width
+ layoutParams.width = LayoutParams.MATCH_PARENT;
+ builder.addMenuPresenter(mActionMenuPresenter);
+ builder.addMenuPresenter(mExpandedMenuPresenter);
+ menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
+ if (mSplitView != null) {
+ mSplitView.addView(menuView, layoutParams);
+ } else {
+ // We'll add this later if we missed it this time.
+ menuView.setLayoutParams(layoutParams);
+ }
+ }
+ mMenuView = menuView;
}
public void setCustomNavigationView(View view) {
@@ -382,18 +428,23 @@ public class ActionBarView extends ViewGroup {
public void setDisplayOptions(int options) {
final int flagsChanged = options ^ mDisplayOptions;
mDisplayOptions = options;
+
+ if ((flagsChanged & ActionBar.DISPLAY_DISABLE_HOME) != 0) {
+ final boolean disableHome = (options & ActionBar.DISPLAY_DISABLE_HOME) != 0;
+ mHomeLayout.setEnabled(!disableHome);
+ }
+
if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
final int vis = (options & ActionBar.DISPLAY_SHOW_HOME) != 0 ? VISIBLE : GONE;
mHomeLayout.setVisibility(vis);
if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- mHomeAsUpView.setVisibility((options & ActionBar.DISPLAY_HOME_AS_UP) != 0
- ? VISIBLE : GONE);
+ mHomeLayout.setUp((options & ActionBar.DISPLAY_HOME_AS_UP) != 0);
}
if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
- mIconView.setImageDrawable(logoVis ? mLogo : mIcon);
+ mHomeLayout.setIcon(logoVis ? mLogo : mIcon);
}
if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
@@ -416,6 +467,59 @@ public class ActionBarView extends ViewGroup {
} else {
invalidate();
}
+
+ // Make sure the home button has an accurate content description for accessibility.
+ if ((options & ActionBar.DISPLAY_DISABLE_HOME) != 0) {
+ mHomeLayout.setContentDescription(null);
+ } else if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ mHomeLayout.setContentDescription(mContext.getResources().getText(
+ R.string.action_bar_up_description));
+ } else {
+ mHomeLayout.setContentDescription(mContext.getResources().getText(
+ R.string.action_bar_home_description));
+ }
+ }
+
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ if (icon != null &&
+ ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
+ mHomeLayout.setIcon(icon);
+ }
+ }
+
+ public void setIcon(int resId) {
+ setIcon(mContext.getResources().getDrawableForDensity(resId, getPreferredIconDensity()));
+ }
+
+ public void setLogo(Drawable logo) {
+ mLogo = logo;
+ if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
+ mHomeLayout.setIcon(logo);
+ }
+ }
+
+ public void setLogo(int resId) {
+ mContext.getResources().getDrawable(resId);
+ }
+
+ /**
+ * @return Drawable density to load that will best fit the available height.
+ */
+ private int getPreferredIconDensity() {
+ final Resources res = mContext.getResources();
+ final int availableHeight = getLayoutParams().height -
+ mHomeLayout.getVerticalIconPadding();
+ int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
+
+ if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) {
+ return DisplayMetrics.DENSITY_LOW;
+ } else if (iconSize * DisplayMetrics.DENSITY_MEDIUM >= availableHeight) {
+ return DisplayMetrics.DENSITY_MEDIUM;
+ } else if (iconSize * DisplayMetrics.DENSITY_HIGH >= availableHeight) {
+ return DisplayMetrics.DENSITY_HIGH;
+ }
+ return DisplayMetrics.DENSITY_XHIGH;
}
public void setNavigationMode(int mode) {
@@ -423,12 +527,12 @@ public class ActionBarView extends ViewGroup {
if (mode != oldMode) {
switch (oldMode) {
case ActionBar.NAVIGATION_MODE_LIST:
- if (mSpinner != null) {
+ if (mListNavLayout != null) {
removeView(mListNavLayout);
}
break;
case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabLayout != null) {
+ if (mTabScrollView != null && mIncludeTabs) {
removeView(mTabScrollView);
}
}
@@ -452,23 +556,26 @@ public class ActionBarView extends ViewGroup {
addView(mListNavLayout);
break;
case ActionBar.NAVIGATION_MODE_TABS:
- ensureTabsExist();
- addView(mTabScrollView);
+ if (mTabScrollView != null && mIncludeTabs) {
+ addView(mTabScrollView);
+ }
break;
}
mNavigationMode = mode;
requestLayout();
}
}
-
- private void ensureTabsExist() {
- if (mTabScrollView == null) {
- mTabScrollView = new HorizontalScrollView(getContext());
- mTabScrollView.setHorizontalFadingEdgeEnabled(true);
- mTabLayout = new LinearLayout(getContext(), null,
- com.android.internal.R.attr.actionBarTabBarStyle);
- mTabScrollView.addView(mTabLayout);
- }
+
+ public ScrollingTabContainerView createTabContainer() {
+ final LinearLayout tabLayout = new LinearLayout(getContext(), null,
+ com.android.internal.R.attr.actionBarTabBarStyle);
+ tabLayout.setMeasureWithLargestChildEnabled(true);
+ tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT, mContentHeight));
+
+ final ScrollingTabContainerView scroller = new ScrollingTabContainerView(mContext);
+ scroller.setTabLayout(tabLayout);
+ return scroller;
}
public void setDropdownAdapter(SpinnerAdapter adapter) {
@@ -502,47 +609,6 @@ public class ActionBarView extends ViewGroup {
return mDisplayOptions;
}
- private TabView createTabView(ActionBar.Tab tab) {
- final TabView tabView = new TabView(getContext(), tab);
- tabView.setFocusable(true);
-
- if (mTabClickListener == null) {
- mTabClickListener = new TabClickListener();
- }
- tabView.setOnClickListener(mTabClickListener);
- return tabView;
- }
-
- public void addTab(ActionBar.Tab tab, boolean setSelected) {
- ensureTabsExist();
- View tabView = createTabView(tab);
- mTabLayout.addView(tabView);
- if (setSelected) {
- tabView.setSelected(true);
- }
- }
-
- public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
- ensureTabsExist();
- final TabView tabView = createTabView(tab);
- mTabLayout.addView(tabView, position);
- if (setSelected) {
- tabView.setSelected(true);
- }
- }
-
- public void removeTabAt(int position) {
- if (mTabLayout != null) {
- mTabLayout.removeViewAt(position);
- }
- }
-
- public void removeAllTabs() {
- if (mTabLayout != null) {
- mTabLayout.removeAllViews();
- }
- }
-
@Override
protected LayoutParams generateDefaultLayoutParams() {
// Used by custom nav views if they don't supply layout params. Everything else
@@ -591,21 +657,34 @@ public class ActionBarView extends ViewGroup {
addView(mTitleLayout);
}
- public void setTabSelected(int position) {
- ensureTabsExist();
- final int tabCount = mTabLayout.getChildCount();
- for (int i = 0; i < tabCount; i++) {
- final View child = mTabLayout.getChildAt(i);
- child.setSelected(i == position);
- }
- }
-
public void setContextView(ActionBarContextView view) {
mContextView = view;
}
+ public void setCollapsable(boolean collapsable) {
+ mIsCollapsable = collapsable;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int childCount = getChildCount();
+ if (mIsCollapsable) {
+ int visibleChildren = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE &&
+ !(child == mMenuView && mMenuView.getChildCount() == 0)) {
+ visibleChildren++;
+ }
+ }
+
+ if (visibleChildren == 0) {
+ // No size for an empty action bar when collapsable.
+ setMeasuredDimension(0, 0);
+ return;
+ }
+ }
+
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
@@ -633,54 +712,59 @@ public class ActionBarView extends ViewGroup {
int leftOfCenter = availableWidth / 2;
int rightOfCenter = leftOfCenter;
- if (mHomeLayout.getVisibility() != GONE) {
- mHomeLayout.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
+
+ if (homeLayout.getVisibility() != GONE) {
+ homeLayout.measure(
+ MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- final int homeWidth = mHomeLayout.getMeasuredWidth();
+ final int homeWidth = homeLayout.getMeasuredWidth();
availableWidth = Math.max(0, availableWidth - homeWidth);
leftOfCenter = Math.max(0, availableWidth - homeWidth);
}
- if (mMenuView != null) {
+ if (mMenuView != null && mMenuView.getParent() == this) {
availableWidth = measureChildView(mMenuView, availableWidth,
childSpecHeight, 0);
rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
}
- boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
- (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
- if (showTitle) {
- availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
- leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
- }
-
- switch (mNavigationMode) {
- case ActionBar.NAVIGATION_MODE_LIST:
- if (mListNavLayout != null) {
- final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
- availableWidth = Math.max(0, availableWidth - itemPaddingSize);
- leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
- mListNavLayout.measure(
- MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- final int listNavWidth = mListNavLayout.getMeasuredWidth();
- availableWidth = Math.max(0, availableWidth - listNavWidth);
- leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
+ if (mExpandedActionView == null) {
+ boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+ (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+ if (showTitle) {
+ availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
+ leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
}
- break;
- case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabScrollView != null) {
- final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
- availableWidth = Math.max(0, availableWidth - itemPaddingSize);
- leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
- mTabScrollView.measure(
- MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- final int tabWidth = mTabScrollView.getMeasuredWidth();
- availableWidth = Math.max(0, availableWidth - tabWidth);
- leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
+
+ switch (mNavigationMode) {
+ case ActionBar.NAVIGATION_MODE_LIST:
+ if (mListNavLayout != null) {
+ final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
+ availableWidth = Math.max(0, availableWidth - itemPaddingSize);
+ leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
+ mListNavLayout.measure(
+ MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ final int listNavWidth = mListNavLayout.getMeasuredWidth();
+ availableWidth = Math.max(0, availableWidth - listNavWidth);
+ leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
+ }
+ break;
+ case ActionBar.NAVIGATION_MODE_TABS:
+ if (mTabScrollView != null) {
+ final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
+ availableWidth = Math.max(0, availableWidth - itemPaddingSize);
+ leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
+ mTabScrollView.measure(
+ MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ final int tabWidth = mTabScrollView.getMeasuredWidth();
+ availableWidth = Math.max(0, availableWidth - tabWidth);
+ leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
+ }
+ break;
}
- break;
}
if (mIndeterminateProgressView != null &&
@@ -691,8 +775,16 @@ public class ActionBarView extends ViewGroup {
rightOfCenter - mIndeterminateProgressView.getMeasuredWidth());
}
- if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
- final LayoutParams lp = generateLayoutParams(mCustomNavView.getLayoutParams());
+ View customView = null;
+ if (mExpandedActionView != null) {
+ customView = mExpandedActionView;
+ } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
+ mCustomNavView != null) {
+ customView = mCustomNavView;
+ }
+
+ if (customView != null) {
+ final LayoutParams lp = generateLayoutParams(customView.getLayoutParams());
final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
(ActionBar.LayoutParams) lp : null;
@@ -729,15 +821,14 @@ public class ActionBarView extends ViewGroup {
customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
}
- mCustomNavView.measure(
+ customView.measure(
MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
}
if (mContentHeight <= 0) {
int measuredHeight = 0;
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
if (paddedViewHeight > measuredHeight) {
@@ -760,51 +851,49 @@ public class ActionBarView extends ViewGroup {
}
}
- private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
- child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
- childSpecHeight);
-
- availableWidth -= child.getMeasuredWidth();
- availableWidth -= spacing;
-
- return Math.max(0, availableWidth);
- }
-
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int x = getPaddingLeft();
final int y = getPaddingTop();
final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
- if (mHomeLayout.getVisibility() != GONE) {
- x += positionChild(mHomeLayout, x, y, contentHeight);
+ if (contentHeight <= 0) {
+ // Nothing to do if we can't see anything.
+ return;
}
-
- final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
- (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
- if (showTitle) {
- x += positionChild(mTitleLayout, x, y, contentHeight);
- }
-
- switch (mNavigationMode) {
- case ActionBar.NAVIGATION_MODE_STANDARD:
- break;
- case ActionBar.NAVIGATION_MODE_LIST:
- if (mListNavLayout != null) {
- if (showTitle) x += mItemPadding;
- x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+
+ View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
+ if (homeLayout.getVisibility() != GONE) {
+ x += positionChild(homeLayout, x, y, contentHeight);
+ }
+
+ if (mExpandedActionView == null) {
+ final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+ (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+ if (showTitle) {
+ x += positionChild(mTitleLayout, x, y, contentHeight);
}
- break;
- case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabScrollView != null) {
- if (showTitle) x += mItemPadding;
- x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+
+ switch (mNavigationMode) {
+ case ActionBar.NAVIGATION_MODE_STANDARD:
+ break;
+ case ActionBar.NAVIGATION_MODE_LIST:
+ if (mListNavLayout != null) {
+ if (showTitle) x += mItemPadding;
+ x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+ }
+ break;
+ case ActionBar.NAVIGATION_MODE_TABS:
+ if (mTabScrollView != null) {
+ if (showTitle) x += mItemPadding;
+ x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+ }
+ break;
}
- break;
}
int menuLeft = r - l - getPaddingRight();
- if (mMenuView != null) {
+ if (mMenuView != null && mMenuView.getParent() == this) {
positionChildInverse(mMenuView, menuLeft, y, contentHeight);
menuLeft -= mMenuView.getMeasuredWidth();
}
@@ -815,13 +904,20 @@ public class ActionBarView extends ViewGroup {
menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
}
- if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
- LayoutParams lp = mCustomNavView.getLayoutParams();
+ View customView = null;
+ if (mExpandedActionView != null) {
+ customView = mExpandedActionView;
+ } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
+ mCustomNavView != null) {
+ customView = mCustomNavView;
+ }
+ if (customView != null) {
+ LayoutParams lp = customView.getLayoutParams();
final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
(ActionBar.LayoutParams) lp : null;
final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
- final int navWidth = mCustomNavView.getMeasuredWidth();
+ final int navWidth = customView.getMeasuredWidth();
int topMargin = 0;
int bottomMargin = 0;
@@ -861,17 +957,17 @@ public class ActionBarView extends ViewGroup {
case Gravity.CENTER_VERTICAL:
final int paddedTop = mTop + getPaddingTop();
final int paddedBottom = mBottom - getPaddingBottom();
- ypos = ((paddedBottom - paddedTop) - mCustomNavView.getMeasuredHeight()) / 2;
+ ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
break;
case Gravity.TOP:
ypos = getPaddingTop() + topMargin;
break;
case Gravity.BOTTOM:
- ypos = getHeight() - getPaddingBottom() - mCustomNavView.getMeasuredHeight()
+ ypos = getHeight() - getPaddingBottom() - customView.getMeasuredHeight()
- bottomMargin;
break;
}
- x += positionChild(mCustomNavView, xpos, ypos, contentHeight);
+ x += positionChild(customView, xpos, ypos, contentHeight);
}
if (mProgressView != null) {
@@ -882,90 +978,83 @@ public class ActionBarView extends ViewGroup {
}
}
- private int positionChild(View child, int x, int y, int contentHeight) {
- int childWidth = child.getMeasuredWidth();
- int childHeight = child.getMeasuredHeight();
- int childTop = y + (contentHeight - childHeight) / 2;
+ @Override
+ public LayoutParams generateLayoutParams(LayoutParams lp) {
+ if (lp == null) {
+ lp = generateDefaultLayoutParams();
+ }
+ return lp;
+ }
- child.layout(x, childTop, x + childWidth, childTop + childHeight);
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState state = new SavedState(superState);
- return childWidth;
- }
-
- private int positionChildInverse(View child, int x, int y, int contentHeight) {
- int childWidth = child.getMeasuredWidth();
- int childHeight = child.getMeasuredHeight();
- int childTop = y + (contentHeight - childHeight) / 2;
+ if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) {
+ state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId();
+ }
- child.layout(x - childWidth, childTop, x, childTop + childHeight);
+ state.isOverflowOpen = isOverflowMenuShowing();
- return childWidth;
+ return state;
}
- private static class TabView extends LinearLayout {
- private ActionBar.Tab mTab;
-
- public TabView(Context context, ActionBar.Tab tab) {
- super(context, null, com.android.internal.R.attr.actionBarTabStyle);
- mTab = tab;
+ @Override
+ public void onRestoreInstanceState(Parcelable p) {
+ SavedState state = (SavedState) p;
- final View custom = tab.getCustomView();
- if (custom != null) {
- addView(custom);
- } else {
- // TODO Style tabs based on the theme
-
- final Drawable icon = tab.getIcon();
- final CharSequence text = tab.getText();
-
- if (icon != null) {
- ImageView iconView = new ImageView(context);
- iconView.setImageDrawable(icon);
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_VERTICAL;
- iconView.setLayoutParams(lp);
- addView(iconView);
- }
+ super.onRestoreInstanceState(state.getSuperState());
- if (text != null) {
- TextView textView = new TextView(context, null,
- com.android.internal.R.attr.actionBarTabTextStyle);
- textView.setText(text);
- textView.setSingleLine();
- textView.setEllipsize(TruncateAt.END);
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_VERTICAL;
- textView.setLayoutParams(lp);
- addView(textView);
- }
+ if (state.expandedMenuItemId != 0 &&
+ mExpandedMenuPresenter != null && mOptionsMenu != null) {
+ final MenuItem item = mOptionsMenu.findItem(state.expandedMenuItemId);
+ if (item != null) {
+ item.expandActionView();
}
-
- setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT, 1));
}
- public ActionBar.Tab getTab() {
- return mTab;
+ if (state.isOverflowOpen) {
+ postShowOverflowMenu();
}
}
- private class TabClickListener implements OnClickListener {
- public void onClick(View view) {
- TabView tabView = (TabView) view;
- tabView.getTab().select();
- final int tabCount = mTabLayout.getChildCount();
- for (int i = 0; i < tabCount; i++) {
- final View child = mTabLayout.getChildAt(i);
- child.setSelected(child == view);
- }
+ static class SavedState extends BaseSavedState {
+ int expandedMenuItemId;
+ boolean isOverflowOpen;
+
+ SavedState(Parcelable superState) {
+ super(superState);
}
+
+ private SavedState(Parcel in) {
+ super(in);
+ expandedMenuItemId = in.readInt();
+ isOverflowOpen = in.readInt() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(expandedMenuItemId);
+ out.writeInt(isOverflowOpen ? 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];
+ }
+ };
}
private static class HomeView extends FrameLayout {
private View mUpView;
- private View mIconView;
+ private ImageView mIconView;
public HomeView(Context context) {
this(context, null);
@@ -975,25 +1064,22 @@ public class ActionBarView extends ViewGroup {
super(context, attrs);
}
+ public void setUp(boolean isUp) {
+ mUpView.setVisibility(isUp ? VISIBLE : GONE);
+ }
+
+ public void setIcon(Drawable icon) {
+ mIconView.setImageDrawable(icon);
+ }
+
@Override
protected void onFinishInflate() {
mUpView = findViewById(com.android.internal.R.id.up);
mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
}
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- // Make sure we reload positioning elements that may change with configuration.
- Resources res = getContext().getResources();
- final int imagePadding = res.getDimensionPixelSize(
- com.android.internal.R.dimen.action_bar_home_image_padding);
- final int upMargin = res.getDimensionPixelSize(
- com.android.internal.R.dimen.action_bar_home_up_margin);
- mIconView.setPadding(imagePadding, getPaddingTop(), imagePadding, getPaddingBottom());
- ((LayoutParams) mUpView.getLayoutParams()).rightMargin = upMargin;
- mUpView.requestLayout();
+ public int getVerticalIconPadding() {
+ return mIconView.getPaddingTop() + mIconView.getPaddingBottom();
}
@Override
@@ -1033,4 +1119,111 @@ public class ActionBarView extends ViewGroup {
mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
}
}
+
+ private class ExpandedActionViewMenuPresenter implements MenuPresenter {
+ MenuBuilder mMenu;
+ MenuItemImpl mCurrentExpandedItem;
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Clear the expanded action view when menus change.
+ mExpandedActionView = null;
+ if (mCurrentExpandedItem != null) {
+ mCurrentExpandedItem.collapseActionView();
+ }
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ return null;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ // Make sure the expanded item we have is still there.
+ if (mCurrentExpandedItem != null) {
+ boolean found = false;
+ final int count = mMenu.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItem item = mMenu.getItem(i);
+ if (item == mCurrentExpandedItem) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // The item we had expanded disappeared. Collapse.
+ collapseItemActionView(mMenu, mCurrentExpandedItem);
+ }
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ mExpandedActionView = item.getActionView();
+ mExpandedHomeLayout.setIcon(item.getIcon());
+ mCurrentExpandedItem = item;
+ if (mExpandedActionView.getParent() != ActionBarView.this) {
+ addView(mExpandedActionView);
+ }
+ if (mExpandedHomeLayout.getParent() != ActionBarView.this) {
+ addView(mExpandedHomeLayout);
+ }
+ mHomeLayout.setVisibility(GONE);
+ mTitleLayout.setVisibility(GONE);
+ if (mTabScrollView != null) mTabScrollView.setVisibility(GONE);
+ if (mSpinner != null) mSpinner.setVisibility(GONE);
+ if (mCustomNavView != null) mCustomNavView.setVisibility(GONE);
+ requestLayout();
+ item.setActionViewExpanded(true);
+ return true;
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ removeView(mExpandedActionView);
+ removeView(mExpandedHomeLayout);
+ if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
+ mHomeLayout.setVisibility(VISIBLE);
+ }
+ if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ 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);
+ }
+ mExpandedActionView = null;
+ mExpandedHomeLayout.setIcon(null);
+ mCurrentExpandedItem = null;
+ requestLayout();
+ item.setActionViewExpanded(false);
+ return true;
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 9f9f020..32e733b 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -18,7 +18,9 @@ package com.android.internal.widget;
import android.os.Bundle;
import android.text.Editable;
+import android.text.Spanned;
import android.text.method.KeyListener;
+import android.text.style.SuggestionSpan;
import android.util.Log;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
@@ -137,6 +139,11 @@ public class EditableInputConnection extends BaseInputConnection {
if (mTextView == null) {
return super.commitText(text, newCursorPosition);
}
+ if (text instanceof Spanned) {
+ Spanned spanned = ((Spanned) text);
+ SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
+ mIMM.registerSuggestionSpansForNotification(spans);
+ }
mTextView.resetErrorChangedFlag();
boolean success = super.commitText(text, newCursorPosition);
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index 5857acb..18076c4 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -22,7 +22,7 @@ import android.widget.RemoteViews;
/** {@hide} */
interface IRemoteViewsFactory {
void onDataSetChanged();
- void onDestroy(in Intent intent);
+ oneway void onDestroy(in Intent intent);
int getCount();
RemoteViews getViewAt(int position);
RemoteViews getLoadingView();
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index c3f6329..d034eab 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -30,6 +30,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.storage.IMountService;
import android.provider.Settings;
+import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -49,7 +50,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Utilities for the lock patten and its settings.
+ * Utilities for the lock pattern and its settings.
*/
public class LockPatternUtils {
@@ -398,12 +399,17 @@ public class LockPatternUtils {
}
raf.close();
DevicePolicyManager dpm = getDevicePolicyManager();
+ KeyStore keyStore = KeyStore.getInstance();
if (pattern != null) {
+ keyStore.password(patternToString(pattern));
setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern
.size(), 0, 0, 0, 0, 0, 0);
} else {
+ if (keyStore.isEmpty()) {
+ keyStore.reset();
+ }
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0,
0, 0, 0, 0, 0);
}
@@ -486,10 +492,14 @@ public class LockPatternUtils {
}
raf.close();
DevicePolicyManager dpm = getDevicePolicyManager();
+ KeyStore keyStore = KeyStore.getInstance();
if (password != null) {
// Update the encryption password.
updateEncryptionPassword(password);
+ // Update the keystore password
+ keyStore.password(password);
+
int computedQuality = computePasswordQuality(password);
setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality));
if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
@@ -542,6 +552,11 @@ public class LockPatternUtils {
}
setString(PASSWORD_HISTORY_KEY, passwordHistory);
} else {
+ // Conditionally reset the keystore if empty. If
+ // non-empty, we are just switching key guard type
+ if (keyStore.isEmpty()) {
+ keyStore.reset();
+ }
dpm.setActivePasswordState(
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
}
@@ -648,7 +663,7 @@ public class LockPatternUtils {
* @param password the gesture pattern.
* @return the hash of the pattern in a byte array.
*/
- public byte[] passwordToHash(String password) {
+ public byte[] passwordToHash(String password) {
if (password == null) {
return null;
}
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
index 65973b6..3070e3e 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -29,7 +29,7 @@ import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
-import android.view.ViewRoot;
+import android.view.ViewAncestor;
import com.android.internal.R;
public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
@@ -150,7 +150,7 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
KeyEvent event = events[i];
event = KeyEvent.changeFlags(event, event.getFlags()
| KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
- handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY, event));
+ handler.sendMessage(handler.obtainMessage(ViewAncestor.DISPATCH_KEY, event));
}
}
}
@@ -158,11 +158,11 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
public void sendDownUpKeyEvents(int keyEventCode) {
long eventTime = SystemClock.uptimeMillis();
Handler handler = mTargetView.getHandler();
- handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ handler.sendMessage(handler.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyEventCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
- handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ handler.sendMessage(handler.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyEventCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 076a1cb..bf1c637 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -320,7 +320,8 @@ public class PointerLocationView extends View {
}
}
- private void logPointerCoords(int action, int index, MotionEvent.PointerCoords coords, int id) {
+ private void logPointerCoords(int action, int index, MotionEvent.PointerCoords coords, int id,
+ int toolType, int buttonState) {
final String prefix;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
@@ -357,6 +358,12 @@ public class PointerLocationView extends View {
case MotionEvent.ACTION_HOVER_MOVE:
prefix = "HOVER MOVE";
break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ prefix = "HOVER ENTER";
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ prefix = "HOVER EXIT";
+ break;
case MotionEvent.ACTION_SCROLL:
prefix = "SCROLL";
break;
@@ -378,28 +385,19 @@ public class PointerLocationView extends View {
.append(" ToolMinor=").append(coords.toolMinor, 3)
.append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
.append("deg")
+ .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(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
+ .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState))
.toString());
}
public void addPointerEvent(MotionEvent event) {
synchronized (mPointers) {
- int action = event.getAction();
-
- //Log.i(TAG, "Motion: action=0x" + Integer.toHexString(action)
- // + " pointers=" + event.getPointerCount());
-
+ final int action = event.getAction();
int NP = mPointers.size();
-
- //mRect.set(0, 0, getWidth(), mHeaderBottom+1);
- //invalidate(mRect);
- //if (mCurDown) {
- // mRect.set(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
- // mCurX+mCurWidth+3, mCurY+mCurWidth+3);
- //} else {
- // mRect.setEmpty();
- //}
+
if (action == MotionEvent.ACTION_DOWN
|| (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
@@ -450,7 +448,8 @@ public class PointerLocationView extends View {
final PointerCoords coords = ps != null ? ps.mCoords : mHoverCoords;
event.getHistoricalPointerCoords(i, historyPos, coords);
if (mPrintCoords) {
- logPointerCoords(action, i, coords, id);
+ logPointerCoords(action, i, coords, id,
+ event.getToolType(i), event.getButtonState());
}
if (ps != null) {
ps.addTrace(coords.x, coords.y);
@@ -463,7 +462,8 @@ public class PointerLocationView extends View {
final PointerCoords coords = ps != null ? ps.mCoords : mHoverCoords;
event.getPointerCoords(i, coords);
if (mPrintCoords) {
- logPointerCoords(action, i, coords, id);
+ logPointerCoords(action, i, coords, id,
+ event.getToolType(i), event.getButtonState());
}
if (ps != null) {
ps.addTrace(coords.x, coords.y);
@@ -494,12 +494,7 @@ public class PointerLocationView extends View {
ps.addTrace(Float.NaN, Float.NaN);
}
}
-
- //if (mCurDown) {
- // mRect.union(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
- // mCurX+mCurWidth+3, mCurY+mCurWidth+3);
- //}
- //invalidate(mRect);
+
postInvalidate();
}
}
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
new file mode 100644
index 0000000..c7d37f2
--- /dev/null
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget;
+
+import android.app.ActionBar;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils.TruncateAt;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class ScrollingTabContainerView extends HorizontalScrollView {
+ Runnable mTabSelector;
+ private TabClickListener mTabClickListener;
+
+ private LinearLayout mTabLayout;
+
+ int mMaxTabWidth;
+
+ public ScrollingTabContainerView(Context context) {
+ super(context);
+ setHorizontalScrollBarEnabled(false);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ setFillViewport(widthMode == MeasureSpec.EXACTLY);
+
+ final int childCount = getChildCount();
+ if (childCount > 1 &&
+ (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
+ if (childCount > 2) {
+ mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f);
+ } else {
+ mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
+ }
+ } else {
+ mMaxTabWidth = -1;
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ public void setTabSelected(int position) {
+ if (mTabLayout == null) {
+ return;
+ }
+
+ final int tabCount = mTabLayout.getChildCount();
+ for (int i = 0; i < tabCount; i++) {
+ final View child = mTabLayout.getChildAt(i);
+ final boolean isSelected = i == position;
+ child.setSelected(isSelected);
+ if (isSelected) {
+ animateToTab(position);
+ }
+ }
+ }
+
+ public void animateToTab(int position) {
+ final View tabView = mTabLayout.getChildAt(position);
+ if (mTabSelector != null) {
+ removeCallbacks(mTabSelector);
+ }
+ mTabSelector = new Runnable() {
+ public void run() {
+ final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
+ smoothScrollTo(scrollPos, 0);
+ mTabSelector = null;
+ }
+ };
+ post(mTabSelector);
+ }
+
+ public void setTabLayout(LinearLayout tabLayout) {
+ if (mTabLayout != tabLayout) {
+ if (mTabLayout != null) {
+ ((ViewGroup) mTabLayout.getParent()).removeView(mTabLayout);
+ }
+ if (tabLayout != null) {
+ addView(tabLayout);
+ }
+ mTabLayout = tabLayout;
+ }
+ }
+
+ public LinearLayout getTabLayout() {
+ return mTabLayout;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mTabSelector != null) {
+ removeCallbacks(mTabSelector);
+ }
+ }
+
+ private TabView createTabView(ActionBar.Tab tab) {
+ final TabView tabView = new TabView(getContext(), tab);
+ tabView.setFocusable(true);
+
+ if (mTabClickListener == null) {
+ mTabClickListener = new TabClickListener();
+ }
+ tabView.setOnClickListener(mTabClickListener);
+ return tabView;
+ }
+
+ public void addTab(ActionBar.Tab tab, boolean setSelected) {
+ View tabView = createTabView(tab);
+ mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0,
+ LayoutParams.MATCH_PARENT, 1));
+ if (setSelected) {
+ tabView.setSelected(true);
+ }
+ }
+
+ public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
+ final TabView tabView = createTabView(tab);
+ mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams(
+ 0, LayoutParams.MATCH_PARENT, 1));
+ if (setSelected) {
+ tabView.setSelected(true);
+ }
+ }
+
+ public void updateTab(int position) {
+ ((TabView) mTabLayout.getChildAt(position)).update();
+ }
+
+ public void removeTabAt(int position) {
+ if (mTabLayout != null) {
+ mTabLayout.removeViewAt(position);
+ }
+ }
+
+ public void removeAllTabs() {
+ if (mTabLayout != null) {
+ mTabLayout.removeAllViews();
+ }
+ }
+
+ private class TabView extends LinearLayout {
+ private ActionBar.Tab mTab;
+ private TextView mTextView;
+ private ImageView mIconView;
+ private View mCustomView;
+
+ public TabView(Context context, ActionBar.Tab tab) {
+ super(context, null, com.android.internal.R.attr.actionBarTabStyle);
+ mTab = tab;
+
+ update();
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // Re-measure if we went beyond our maximum size.
+ if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) {
+ super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
+ heightMeasureSpec);
+ }
+ }
+
+ public void update() {
+ final ActionBar.Tab tab = mTab;
+ final View custom = tab.getCustomView();
+ if (custom != null) {
+ addView(custom);
+ mCustomView = custom;
+ if (mTextView != null) mTextView.setVisibility(GONE);
+ if (mIconView != null) {
+ mIconView.setVisibility(GONE);
+ mIconView.setImageDrawable(null);
+ }
+ } else {
+ if (mCustomView != null) {
+ removeView(mCustomView);
+ mCustomView = null;
+ }
+
+ final Drawable icon = tab.getIcon();
+ final CharSequence text = tab.getText();
+
+ if (icon != null) {
+ if (mIconView == null) {
+ ImageView iconView = new ImageView(getContext());
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ iconView.setLayoutParams(lp);
+ addView(iconView, 0);
+ mIconView = iconView;
+ }
+ mIconView.setImageDrawable(icon);
+ mIconView.setVisibility(VISIBLE);
+ } else if (mIconView != null) {
+ mIconView.setVisibility(GONE);
+ mIconView.setImageDrawable(null);
+ }
+
+ if (text != null) {
+ if (mTextView == null) {
+ TextView textView = new TextView(getContext(), null,
+ com.android.internal.R.attr.actionBarTabTextStyle);
+ textView.setSingleLine();
+ textView.setEllipsize(TruncateAt.END);
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ textView.setLayoutParams(lp);
+ addView(textView);
+ mTextView = textView;
+ }
+ mTextView.setText(text);
+ mTextView.setVisibility(VISIBLE);
+ } else {
+ mTextView.setVisibility(GONE);
+ }
+ }
+ }
+
+ public ActionBar.Tab getTab() {
+ return mTab;
+ }
+ }
+
+ private class TabClickListener implements OnClickListener {
+ public void onClick(View view) {
+ TabView tabView = (TabView) view;
+ tabView.getTab().select();
+ final int tabCount = mTabLayout.getChildCount();
+ for (int i = 0; i < tabCount; i++) {
+ final View child = mTabLayout.getChildAt(i);
+ child.setSelected(child == view);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java
index aee7b76..e113dd8 100644
--- a/core/java/com/android/internal/widget/TextProgressBar.java
+++ b/core/java/com/android/internal/widget/TextProgressBar.java
@@ -86,7 +86,8 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick
// Check if Chronometer should move with with ProgressBar
mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT);
- mChronometerGravity = (mChronometer.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK);
+ mChronometerGravity = (mChronometer.getGravity() &
+ Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK);
} else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) {
mProgressBar = (ProgressBar) child;
diff --git a/core/java/com/google/android/mms/pdu/EncodedStringValue.java b/core/java/com/google/android/mms/pdu/EncodedStringValue.java
index a27962d..9495c1c 100644
--- a/core/java/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/core/java/com/google/android/mms/pdu/EncodedStringValue.java
@@ -17,7 +17,6 @@
package com.google.android.mms.pdu;
-import android.util.Config;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -31,7 +30,7 @@ import java.util.ArrayList;
public class EncodedStringValue implements Cloneable {
private static final String TAG = "EncodedStringValue";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
/**
* The Char-set value.
diff --git a/core/java/com/google/android/mms/pdu/PduParser.java b/core/java/com/google/android/mms/pdu/PduParser.java
index 3f185aa..f7f71ed 100755
--- a/core/java/com/google/android/mms/pdu/PduParser.java
+++ b/core/java/com/google/android/mms/pdu/PduParser.java
@@ -20,7 +20,6 @@ package com.google.android.mms.pdu;
import com.google.android.mms.ContentType;
import com.google.android.mms.InvalidHeaderValueException;
-import android.util.Config;
import android.util.Log;
import java.io.ByteArrayInputStream;
@@ -86,7 +85,7 @@ public class PduParser {
*/
private static final String LOG_TAG = "PduParser";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
/**
* Constructor.
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index 9fdd204..4d2d535 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -39,7 +39,6 @@ import android.provider.Telephony.Mms.Addr;
import android.provider.Telephony.Mms.Part;
import android.provider.Telephony.MmsSms.PendingMessages;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -63,7 +62,7 @@ import com.google.android.mms.pdu.EncodedStringValue;
public class PduPersister {
private static final String TAG = "PduPersister";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
diff --git a/core/java/com/google/android/mms/util/AbstractCache.java b/core/java/com/google/android/mms/util/AbstractCache.java
index 670439c..39b2abf 100644
--- a/core/java/com/google/android/mms/util/AbstractCache.java
+++ b/core/java/com/google/android/mms/util/AbstractCache.java
@@ -17,7 +17,6 @@
package com.google.android.mms.util;
-import android.util.Config;
import android.util.Log;
import java.util.HashMap;
@@ -25,7 +24,7 @@ import java.util.HashMap;
public abstract class AbstractCache<K, V> {
private static final String TAG = "AbstractCache";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final int MAX_CACHED_ITEMS = 500;
diff --git a/core/java/com/google/android/mms/util/PduCache.java b/core/java/com/google/android/mms/util/PduCache.java
index 866ca1e..059af72 100644
--- a/core/java/com/google/android/mms/util/PduCache.java
+++ b/core/java/com/google/android/mms/util/PduCache.java
@@ -21,7 +21,6 @@ import android.content.ContentUris;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.Telephony.Mms;
-import android.util.Config;
import android.util.Log;
import java.util.HashMap;
@@ -30,7 +29,7 @@ import java.util.HashSet;
public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
private static final String TAG = "PduCache";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final int MMS_ALL = 0;
private static final int MMS_ALL_ID = 1;